diff --git a/src/PPDS.Auth/CHANGELOG.md b/src/PPDS.Auth/CHANGELOG.md index 4001de8d8..0dd1b924c 100644 --- a/src/PPDS.Auth/CHANGELOG.md +++ b/src/PPDS.Auth/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **ServiceClient org metadata not populated** - Credential providers now use `ConnectionOptions` constructor instead of token provider constructor, which triggers org metadata discovery. This populates `ConnectedOrgFriendlyName`, `ConnectedOrgUniqueName`, and `ConnectedOrgId` properties. ([#86](https://github.com/joshsmithxrm/ppds-sdk/issues/86)) + ### Added - **Integration tests for credential providers** - Live tests for `ClientSecretCredentialProvider`, `CertificateFileCredentialProvider`, and `GitHubFederatedCredentialProvider` ([#55](https://github.com/joshsmithxrm/ppds-sdk/issues/55)) diff --git a/src/PPDS.Auth/Credentials/AzureDevOpsFederatedCredentialProvider.cs b/src/PPDS.Auth/Credentials/AzureDevOpsFederatedCredentialProvider.cs index 5bf0a241a..b32f18a9e 100644 --- a/src/PPDS.Auth/Credentials/AzureDevOpsFederatedCredentialProvider.cs +++ b/src/PPDS.Auth/Credentials/AzureDevOpsFederatedCredentialProvider.cs @@ -4,6 +4,7 @@ using Azure.Core; using Azure.Identity; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.PowerPlatform.Dataverse.Client.Model; using PPDS.Auth.Cloud; using PPDS.Auth.Profiles; @@ -76,12 +77,14 @@ public async Task CreateServiceClientAsync( EnsureCredentialInitialized(); - var token = await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false); - - var client = new ServiceClient( - new Uri(environmentUrl), - _ => Task.FromResult(token), - useUniqueInstance: true); + // Create ServiceClient using ConnectionOptions to ensure org metadata discovery. + // The provider function acquires tokens on demand and refreshes when needed. + var options = new ConnectionOptions + { + ServiceUri = new Uri(environmentUrl), + AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None) + }; + var client = new ServiceClient(options); if (!client.IsReady) { diff --git a/src/PPDS.Auth/Credentials/DeviceCodeCredentialProvider.cs b/src/PPDS.Auth/Credentials/DeviceCodeCredentialProvider.cs index b53816932..99573637c 100644 --- a/src/PPDS.Auth/Credentials/DeviceCodeCredentialProvider.cs +++ b/src/PPDS.Auth/Credentials/DeviceCodeCredentialProvider.cs @@ -6,6 +6,7 @@ using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.PowerPlatform.Dataverse.Client.Model; using PPDS.Auth.Cloud; using PPDS.Auth.Profiles; @@ -107,14 +108,17 @@ public async Task CreateServiceClientAsync( // Ensure MSAL client is initialized await EnsureMsalClientInitializedAsync().ConfigureAwait(false); - // Get token - var token = await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false); + // Get token and prime the cache (may prompt user for device code auth) + await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false); - // Create ServiceClient with token provider - var client = new ServiceClient( - new Uri(environmentUrl), - _ => Task.FromResult(token), - useUniqueInstance: true); + // Create ServiceClient using ConnectionOptions to ensure org metadata discovery. + // The provider function uses cached tokens and refreshes silently when needed. + var options = new ConnectionOptions + { + ServiceUri = new Uri(environmentUrl), + AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, forceInteractive: false, CancellationToken.None) + }; + var client = new ServiceClient(options); if (!client.IsReady) { diff --git a/src/PPDS.Auth/Credentials/GitHubFederatedCredentialProvider.cs b/src/PPDS.Auth/Credentials/GitHubFederatedCredentialProvider.cs index 2247c51ec..e94bbdd77 100644 --- a/src/PPDS.Auth/Credentials/GitHubFederatedCredentialProvider.cs +++ b/src/PPDS.Auth/Credentials/GitHubFederatedCredentialProvider.cs @@ -4,6 +4,7 @@ using Azure.Core; using Azure.Identity; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.PowerPlatform.Dataverse.Client.Model; using PPDS.Auth.Cloud; using PPDS.Auth.Profiles; @@ -76,12 +77,14 @@ public async Task CreateServiceClientAsync( EnsureCredentialInitialized(); - var token = await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false); - - var client = new ServiceClient( - new Uri(environmentUrl), - _ => Task.FromResult(token), - useUniqueInstance: true); + // Create ServiceClient using ConnectionOptions to ensure org metadata discovery. + // The provider function acquires tokens on demand and refreshes when needed. + var options = new ConnectionOptions + { + ServiceUri = new Uri(environmentUrl), + AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None) + }; + var client = new ServiceClient(options); if (!client.IsReady) { diff --git a/src/PPDS.Auth/Credentials/InteractiveBrowserCredentialProvider.cs b/src/PPDS.Auth/Credentials/InteractiveBrowserCredentialProvider.cs index 318bf5980..f23655439 100644 --- a/src/PPDS.Auth/Credentials/InteractiveBrowserCredentialProvider.cs +++ b/src/PPDS.Auth/Credentials/InteractiveBrowserCredentialProvider.cs @@ -8,6 +8,7 @@ using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.PowerPlatform.Dataverse.Client.Model; using PPDS.Auth.Cloud; using PPDS.Auth.Profiles; @@ -140,14 +141,17 @@ public async Task CreateServiceClientAsync( // Ensure MSAL client is initialized await EnsureMsalClientInitializedAsync().ConfigureAwait(false); - // Get token - var token = await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false); + // Get token and prime the cache (may prompt user for interactive auth) + await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false); - // Create ServiceClient with token provider - var client = new ServiceClient( - new Uri(environmentUrl), - _ => Task.FromResult(token), - useUniqueInstance: true); + // Create ServiceClient using ConnectionOptions to ensure org metadata discovery. + // The provider function uses cached tokens and refreshes silently when needed. + var options = new ConnectionOptions + { + ServiceUri = new Uri(environmentUrl), + AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, forceInteractive: false, CancellationToken.None) + }; + var client = new ServiceClient(options); if (!client.IsReady) { diff --git a/src/PPDS.Auth/Credentials/ManagedIdentityCredentialProvider.cs b/src/PPDS.Auth/Credentials/ManagedIdentityCredentialProvider.cs index fb66b7368..2f89a31a3 100644 --- a/src/PPDS.Auth/Credentials/ManagedIdentityCredentialProvider.cs +++ b/src/PPDS.Auth/Credentials/ManagedIdentityCredentialProvider.cs @@ -4,6 +4,7 @@ using Azure.Core; using Azure.Identity; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.PowerPlatform.Dataverse.Client.Model; using PPDS.Auth.Profiles; namespace PPDS.Auth.Credentials; @@ -90,17 +91,17 @@ public async Task CreateServiceClientAsync( // Normalize URL environmentUrl = environmentUrl.TrimEnd('/'); - // Get token - var token = await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false); - - // Create ServiceClient with token provider + // Create ServiceClient using ConnectionOptions to ensure org metadata discovery. + // The provider function acquires tokens on demand and refreshes when needed. ServiceClient client; try { - client = new ServiceClient( - new Uri(environmentUrl), - _ => Task.FromResult(token), - useUniqueInstance: true); + var options = new ConnectionOptions + { + ServiceUri = new Uri(environmentUrl), + AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None) + }; + client = new ServiceClient(options); } catch (Exception ex) {