From 6a82e7b5175dc06dcd3113aa163338e974581527 Mon Sep 17 00:00:00 2001 From: Prangshuman Das Date: Fri, 24 Jul 2026 14:09:38 +0200 Subject: [PATCH 1/3] Expose AppService location and EUDB membership on Environment Information Add GetApplicationServiceLocation() and IsApplicationServiceInEUDB() to the public Environment Information codeunit (and its Impl), wrapping the platform DotNet NavTenantSettingsHelper methods GetAppServiceLocation() and GetAppServiceInEUDB(). This lets apps read the hosting location and EU Data Boundary membership through the managed System Application API instead of referencing the DotNet type directly, which forces target: OnPrem. Draft for discussion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d33addc7-7b1b-47bd-b3dc-2a723ed7885d --- .../src/EnvironmentInformation.Codeunit.al | 18 ++++++++++++++++++ .../src/EnvironmentInformationImpl.Codeunit.al | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al b/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al index 49991607637..2ad3660dd63 100644 --- a/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al +++ b/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al @@ -155,6 +155,24 @@ codeunit 457 "Environment Information" exit(EnvironmentInformationImpl.GetLinkedPowerPlatformEnvironmentId()); end; + /// + /// Gets the physical location of the application service the environment is hosted on (for example, "Canada Central"). + /// + /// The application service location if available; otherwise, an empty string. + procedure GetApplicationServiceLocation(): Text + begin + exit(EnvironmentInformationImpl.GetApplicationServiceLocation()); + end; + + /// + /// Checks whether the Azure geography of the application service the environment is hosted on is within the Microsoft EU Data Boundary (EUDB). + /// + /// True if the environment is hosted within the EU Data Boundary, false otherwise. + procedure IsApplicationServiceInEUDB(): Boolean + begin + exit(EnvironmentInformationImpl.IsApplicationServiceInEUDB()); + end; + /// /// Gets the value of the specified environment setting. This is only callable from Microsoft published apps. /// diff --git a/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al b/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al index 5631edee889..6131a6ec04d 100644 --- a/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al +++ b/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al @@ -177,6 +177,16 @@ codeunit 3702 "Environment Information Impl." exit(NavTenantSettingsHelper.GetLinkedPowerPlatformEnvironmentId()); end; + procedure GetApplicationServiceLocation(): Text + begin + exit(NavTenantSettingsHelper.GetAppServiceLocation()); + end; + + procedure IsApplicationServiceInEUDB(): Boolean + begin + exit(NavTenantSettingsHelper.GetAppServiceInEUDB()); + end; + procedure GetEnvironmentSetting(SettingName: Text; ModuleInfo: ModuleInfo): Text begin if ModuleInfo.Publisher <> 'Microsoft' then From f18377edecf4e26821d66352bd66fdfaecfdff8f Mon Sep 17 00:00:00 2001 From: Prangshuman Das Date: Mon, 27 Jul 2026 13:36:43 +0200 Subject: [PATCH 2/3] Harden AppService environment APIs Restrict the new Environment Information APIs to Microsoft-published callers and define explicit non-SaaS fallback behavior. Add focused Microsoft and partner caller tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcb1f91e-583b-42e0-b3a9-3c9953336fc4 --- .../src/EnvironmentInformation.Codeunit.al | 18 ++++--- .../EnvironmentInformationImpl.Codeunit.al | 21 +++++++- ...ironmentInformationTestPartner.Codeunit.al | 48 +++++++++++++++++++ .../EnvironmentInformationTest.Codeunit.al | 28 +++++++++++ 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al diff --git a/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al b/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al index 2ad3660dd63..c4d64044178 100644 --- a/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al +++ b/src/System Application/App/Environment Information/src/EnvironmentInformation.Codeunit.al @@ -156,21 +156,27 @@ codeunit 457 "Environment Information" end; /// - /// Gets the physical location of the application service the environment is hosted on (for example, "Canada Central"). + /// Gets the physical location of the application service the environment is hosted on (for example, "Canada Central"). This is only callable from Microsoft published apps. /// - /// The application service location if available; otherwise, an empty string. + /// The application service location when running on SaaS infrastructure and the information is available; otherwise, an empty string. procedure GetApplicationServiceLocation(): Text + var + CallerModuleInfo: ModuleInfo; begin - exit(EnvironmentInformationImpl.GetApplicationServiceLocation()); + NavApp.GetCallerModuleInfo(CallerModuleInfo); + exit(EnvironmentInformationImpl.GetApplicationServiceLocation(CallerModuleInfo)); end; /// - /// Checks whether the Azure geography of the application service the environment is hosted on is within the Microsoft EU Data Boundary (EUDB). + /// Checks whether the Azure geography of the application service the environment is hosted on is within the Microsoft EU Data Boundary (EUDB). This is only callable from Microsoft published apps. /// - /// True if the environment is hosted within the EU Data Boundary, false otherwise. + /// True when the environment is hosted within the EU Data Boundary. False means either that the environment is outside the EU Data Boundary or that the information is unavailable, including when not running on SaaS infrastructure. procedure IsApplicationServiceInEUDB(): Boolean + var + CallerModuleInfo: ModuleInfo; begin - exit(EnvironmentInformationImpl.IsApplicationServiceInEUDB()); + NavApp.GetCallerModuleInfo(CallerModuleInfo); + exit(EnvironmentInformationImpl.IsApplicationServiceInEUDB(CallerModuleInfo)); end; /// diff --git a/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al b/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al index 6131a6ec04d..a3904a169df 100644 --- a/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al +++ b/src/System Application/App/Environment Information/src/EnvironmentInformationImpl.Codeunit.al @@ -27,6 +27,7 @@ codeunit 3702 "Environment Information Impl." IsSandboxInitialized: Boolean; DefaultSandboxEnvironmentNameTxt: Label 'Sandbox', Locked = true; DefaultProductionEnvironmentNameTxt: Label 'Production', Locked = true; + MicrosoftPublisherOnlyErr: Label 'This procedure is only available for Microsoft published apps.'; procedure IsProduction(): Boolean begin @@ -177,13 +178,23 @@ codeunit 3702 "Environment Information Impl." exit(NavTenantSettingsHelper.GetLinkedPowerPlatformEnvironmentId()); end; - procedure GetApplicationServiceLocation(): Text + procedure GetApplicationServiceLocation(CallerModuleInfo: ModuleInfo): Text begin + EnsureMicrosoftPublisher(CallerModuleInfo); + + if not IsSaaSInfrastructure() then + exit(''); + exit(NavTenantSettingsHelper.GetAppServiceLocation()); end; - procedure IsApplicationServiceInEUDB(): Boolean + procedure IsApplicationServiceInEUDB(CallerModuleInfo: ModuleInfo): Boolean begin + EnsureMicrosoftPublisher(CallerModuleInfo); + + if not IsSaaSInfrastructure() then + exit(false); + exit(NavTenantSettingsHelper.GetAppServiceInEUDB()); end; @@ -194,6 +205,12 @@ codeunit 3702 "Environment Information Impl." exit(NavTenantSettingsHelper.GetEnvironmentApplicationSetting(SettingName)); end; + local procedure EnsureMicrosoftPublisher(CallerModuleInfo: ModuleInfo) + begin + if CallerModuleInfo.Publisher <> 'Microsoft' then + Error(MicrosoftPublisherOnlyErr); + end; + [InternalEvent(false)] procedure OnBeforeGetApplicationIdentifier(var AppId: Text) begin diff --git a/src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al b/src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al new file mode 100644 index 00000000000..9d29adf8b6d --- /dev/null +++ b/src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Partner.Test.Environment; + +using System.Environment; +using System.TestLibraries.Utilities; + +codeunit 139023 "Environment Info Test Partner" +{ + Subtype = Test; + + var + Assert: Codeunit "Library Assert"; + MicrosoftPublisherOnlyErr: Label 'This procedure is only available for Microsoft published apps.'; + + [Test] + procedure GetApplicationServiceLocationRequiresMicrosoftPublisher() + var + EnvironmentInformation: Codeunit "Environment Information"; + ApplicationServiceLocation: Text; + begin + // [SCENARIO] A partner app cannot read the application service location. + + // [WHEN] A partner-published app requests the application service location + asserterror ApplicationServiceLocation := EnvironmentInformation.GetApplicationServiceLocation(); + + // [THEN] Access is denied + Assert.ExpectedError(MicrosoftPublisherOnlyErr); + end; + + [Test] + procedure IsApplicationServiceInEUDBRequiresMicrosoftPublisher() + var + EnvironmentInformation: Codeunit "Environment Information"; + IsInEUDB: Boolean; + begin + // [SCENARIO] A partner app cannot read application service EUDB membership. + + // [WHEN] A partner-published app requests EUDB membership + asserterror IsInEUDB := EnvironmentInformation.IsApplicationServiceInEUDB(); + + // [THEN] Access is denied + Assert.ExpectedError(MicrosoftPublisherOnlyErr); + end; +} diff --git a/src/System Application/Test/Environment Information/src/EnvironmentInformationTest.Codeunit.al b/src/System Application/Test/Environment Information/src/EnvironmentInformationTest.Codeunit.al index 1fea6b450ed..75b336a5776 100644 --- a/src/System Application/Test/Environment Information/src/EnvironmentInformationTest.Codeunit.al +++ b/src/System Application/Test/Environment Information/src/EnvironmentInformationTest.Codeunit.al @@ -85,6 +85,34 @@ codeunit 135091 "Environment Information Test" Assert.IsFalse(EnvironmentInformation.IsSaaS(), 'Testability should have dictacted a non- SaaS environment'); end; + [Test] + [Scope('OnPrem')] + procedure TestApplicationServiceLocationIsEmptyOutsideSaaSInfrastructure() + begin + // [SCENARIO] Application service location is unavailable outside SaaS infrastructure. + + // [GIVEN] SaaS testability is disabled + EnvironmentInfoTestLibrary.SetTestabilitySoftwareAsAService(false); + + // [WHEN] The application service location is requested + // [THEN] An empty location is returned + Assert.AreEqual('', EnvironmentInformation.GetApplicationServiceLocation(), 'Application service location should be empty outside SaaS infrastructure.'); + end; + + [Test] + [Scope('OnPrem')] + procedure TestApplicationServiceEUDBIsFalseOutsideSaaSInfrastructure() + begin + // [SCENARIO] EUDB membership is unavailable outside SaaS infrastructure. + + // [GIVEN] SaaS testability is disabled + EnvironmentInfoTestLibrary.SetTestabilitySoftwareAsAService(false); + + // [WHEN] EUDB membership is requested + // [THEN] False is returned + Assert.IsFalse(EnvironmentInformation.IsApplicationServiceInEUDB(), 'Application service EUDB membership should be false outside SaaS infrastructure.'); + end; + [Test] [Scope('OnPrem')] procedure TestIsEarlyPreviewVersionIsSet() From fe2e3801b4d966fa2da402e4e8e2a3ad31694f44 Mon Sep 17 00:00:00 2001 From: Prangshuman Das Date: Mon, 27 Jul 2026 15:32:00 +0200 Subject: [PATCH 3/3] Fix Environment Information partner test analyzers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcb1f91e-583b-42e0-b3a9-3c9953336fc4 --- ...tner.Codeunit.al => EnvironmentInfoTestPartner.Codeunit.al} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/System Application/Partner Test/Environment Information/src/{EnvironmentInformationTestPartner.Codeunit.al => EnvironmentInfoTestPartner.Codeunit.al} (93%) diff --git a/src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al b/src/System Application/Partner Test/Environment Information/src/EnvironmentInfoTestPartner.Codeunit.al similarity index 93% rename from src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al rename to src/System Application/Partner Test/Environment Information/src/EnvironmentInfoTestPartner.Codeunit.al index 9d29adf8b6d..29c91c2c2be 100644 --- a/src/System Application/Partner Test/Environment Information/src/EnvironmentInformationTestPartner.Codeunit.al +++ b/src/System Application/Partner Test/Environment Information/src/EnvironmentInfoTestPartner.Codeunit.al @@ -35,12 +35,11 @@ codeunit 139023 "Environment Info Test Partner" procedure IsApplicationServiceInEUDBRequiresMicrosoftPublisher() var EnvironmentInformation: Codeunit "Environment Information"; - IsInEUDB: Boolean; begin // [SCENARIO] A partner app cannot read application service EUDB membership. // [WHEN] A partner-published app requests EUDB membership - asserterror IsInEUDB := EnvironmentInformation.IsApplicationServiceInEUDB(); + asserterror EnvironmentInformation.IsApplicationServiceInEUDB(); // [THEN] Access is denied Assert.ExpectedError(MicrosoftPublisherOnlyErr);