diff --git a/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformAdminClient.Codeunit.al b/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformAdminClient.Codeunit.al index 35bed8f9e05..633ab0f414b 100644 --- a/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformAdminClient.Codeunit.al +++ b/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformAdminClient.Codeunit.al @@ -21,6 +21,7 @@ codeunit 48526 "Fabric Platform Admin Client" PrincipalIdRequiredErr: Label 'Principal ID is missing. Ask your Microsoft Entra administrator for the object ID of the enterprise application, enter it on the Fabric setup page, and try again.'; ReadPrincipalIdSetupInstructionsLbl: Label 'Read setup instructions'; PrincipalIdSetupHelpUrlTok: Label 'https://go.microsoft.com/fwlink/?LinkId=2386618', Locked = true; + AddSPTokenErr: Label 'Service principal could not be added to the Fabric workspace. Add it directly in Fabric with Contributor role.'; AddSPTransportErr: Label 'Failed to add service principal to workspace: transport error.'; AddSPHttpGenericErr: Label 'Failed to add service principal to workspace. Contact your administrator if the problem persists.'; AddSPHttpStatusErr: Label 'Failed to add service principal to workspace. HTTP %1.', Comment = '%1 = HTTP status code'; @@ -56,7 +57,7 @@ codeunit 48526 "Fabric Platform Admin Client" TempBuffer.Reset(); TempBuffer.DeleteAll(false); - AccessToken := CredMgt.AcquireFabricApiTokenDelegated(); + AccessToken := CredMgt.AcquireFabricApiTokenDelegated(false); RestClientResult := HttpClient.CreateClientWithBearer(AccessToken); Req := HttpClient.BuildJsonRequest('GET', 'https://api.fabric.microsoft.com/v1/workspaces', ''); if not HttpClient.TrySend(RestClientResult, Req, Resp) then @@ -112,7 +113,7 @@ codeunit 48526 "Fabric Platform Admin Client" TempBuffer.Reset(); TempBuffer.DeleteAll(false); - AccessToken := CredMgt.AcquireFabricApiTokenDelegated(); + AccessToken := CredMgt.AcquireFabricApiTokenDelegated(false); RestClientResult := HttpClient.CreateClientWithBearer(AccessToken); Req := HttpClient.BuildJsonRequest( 'GET', @@ -143,7 +144,6 @@ codeunit 48526 "Fabric Platform Admin Client" procedure AddServicePrincipalToWorkspace(WorkspaceId: Text; PrincipalId: Text) var - CredMgt: Codeunit "Fabric Platform Credential Mgt"; HttpClient: Codeunit "Fabric Platform Http Client"; FabricPrivacyNotice: Codeunit "Fabric Privacy Notice"; RestClientResult: Codeunit "Rest Client"; @@ -165,7 +165,8 @@ codeunit 48526 "Fabric Platform Admin Client" if WorkspaceIdForUrl = '' then Error(WorkspaceRequiredForSPErr); - AccessToken := CredMgt.AcquireFabricApiTokenDelegated(); + if not TryAcquireWorkspaceWriteToken(AccessToken) then + Error(CreateAddSPTokenErrorInfo(GetLastErrorText())); PrincipalObj.Add('id', PrincipalId); PrincipalObj.Add('type', 'ServicePrincipal'); @@ -190,6 +191,15 @@ codeunit 48526 "Fabric Platform Admin Client" Error(CreateAddSPHttpErrorInfo(Resp.GetHttpStatusCode())); end; + [TryFunction] + [NonDebuggable] + local procedure TryAcquireWorkspaceWriteToken(var AccessToken: SecretText) + var + CredMgt: Codeunit "Fabric Platform Credential Mgt"; + begin + AccessToken := CredMgt.AcquireFabricApiTokenDelegated(true); + end; + local procedure HasRequiredWorkspaceRole(WorkspaceId: Text; PrincipalId: Text; AccessToken: SecretText): Boolean var HttpClient: Codeunit "Fabric Platform Http Client"; @@ -301,6 +311,14 @@ codeunit 48526 "Fabric Platform Admin Client" Hyperlink(PrincipalIdSetupHelpUrlTok); end; + local procedure CreateAddSPTokenErrorInfo(DetailedMessage: Text) ErrInfo: ErrorInfo + begin + ErrInfo.Message(AddSPTokenErr); + ErrInfo.DetailedMessage(DetailedMessage); + ErrInfo.DataClassification(DataClassification::CustomerContent); + ErrInfo.AddAction(ReadPrincipalIdSetupInstructionsLbl, Codeunit::"Fabric Platform Admin Client", 'OpenPrincipalIdSetupHelp'); + end; + local procedure CreateRetrieveWorkspacesHttpErrorInfo(StatusCode: Integer) ErrInfo: ErrorInfo begin ErrInfo.Message(RetrieveWorkspacesHttpGenericErr); diff --git a/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformCredentialMgt.Codeunit.al b/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformCredentialMgt.Codeunit.al index 20663732568..ccf3590a5bc 100644 --- a/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformCredentialMgt.Codeunit.al +++ b/src/Apps/W1/MicrosoftFabricMirroring/app/src/platform/FabricPlatformCredentialMgt.Codeunit.al @@ -180,11 +180,8 @@ codeunit 48524 "Fabric Platform Credential Mgt" LookupState.ClearFabricApiToken(); end; - /// - /// Acquires a delegated Fabric API token via On-Behalf-Of for the first-party app. - /// [NonDebuggable] - procedure AcquireFabricApiTokenDelegated(): SecretText + procedure AcquireFabricApiTokenDelegated(RequireWriteAccess: Boolean): SecretText var OAuth2: Codeunit OAuth2; LookupState: Codeunit "Fabric Platform Lookup State"; @@ -192,17 +189,26 @@ codeunit 48524 "Fabric Platform Credential Mgt" RedirectUrl: Text; Scopes: List of [Text]; begin - // Test-injection seam: unit tests pre-seed a token to bypass the platform flow. if LookupState.HasFabricApiToken() then exit(LookupState.GetFabricApiToken()); if IsCustomAppEnabled() then exit(AcquireFabricApiTokenWithCustomApp()); - Scopes.Add('https://api.fabric.microsoft.com/Workspace.ReadWrite.All'); OAuth2.GetDefaultRedirectUrl(RedirectUrl); - OAuth2.AcquireOnBehalfOfToken(RedirectUrl, Scopes, AccessToken); - if AccessToken.IsEmpty() then + if not RequireWriteAccess then begin + Scopes.Add('https://api.fabric.microsoft.com/Workspace.Read.All'); + if OAuth2.AcquireOnBehalfOfToken(RedirectUrl, Scopes, AccessToken) then + if not AccessToken.IsEmpty() then + exit(AccessToken); + + Clear(Scopes); + Clear(AccessToken); + ClearLastError(); + end; + + Scopes.Add('https://api.fabric.microsoft.com/Workspace.ReadWrite.All'); + if (not OAuth2.AcquireOnBehalfOfToken(RedirectUrl, Scopes, AccessToken)) or AccessToken.IsEmpty() then Error(FabricApiTokenErr); exit(AccessToken); end;