Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/PPDS.Auth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### 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))
- **ServiceClient org metadata not populated** - Credential providers now force eager org metadata discovery by accessing `ConnectedOrgFriendlyName` immediately after creating the ServiceClient. Discovery is lazy by default, and the connection pool clones clients before properties are accessed, resulting in empty metadata. This fix ensures `ConnectedOrgFriendlyName`, `ConnectedOrgUniqueName`, and `ConnectedOrgId` are populated. ([#86](https://github.com/joshsmithxrm/ppds-sdk/issues/86))

### Added

- **`EnvironmentResolutionService`** - Multi-layer environment resolution that tries direct Dataverse connection first (works for service principals), then falls back to Global Discovery for user authentication. Returns full org metadata. ([#89](https://github.com/joshsmithxrm/ppds-sdk/issues/89), [#88](https://github.com/joshsmithxrm/ppds-sdk/issues/88))
- **Integration tests for credential providers** - Live tests for `ClientSecretCredentialProvider`, `CertificateFileCredentialProvider`, and `GitHubFederatedCredentialProvider` ([#55](https://github.com/joshsmithxrm/ppds-sdk/issues/55))
- Manual test procedures documentation for interactive browser and device code authentication

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,24 @@ public async Task<ServiceClient> CreateServiceClientAsync(

EnsureCredentialInitialized();

// Create ServiceClient using ConnectionOptions to ensure org metadata discovery.
// The provider function acquires tokens on demand and refreshes when needed.
// Get token and prime the cache (uses cancellationToken for cancellable first request)
await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false);

// Create ServiceClient using ConnectionOptions.
// The provider function uses cached tokens and refreshes when needed.
var options = new ConnectionOptions
{
ServiceUri = new Uri(environmentUrl),
AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None)
};
var client = new ServiceClient(options);

// Force org metadata discovery before client is cloned by pool.
// ServiceClient uses lazy initialization - properties like ConnectedOrgFriendlyName
// are only populated when first accessed. The connection pool clones clients before
// properties are accessed, so clones would have empty metadata.
_ = client.ConnectedOrgFriendlyName;

if (!client.IsReady)
{
var error = client.LastError ?? "Unknown error";
Expand Down
8 changes: 7 additions & 1 deletion src/PPDS.Auth/Credentials/DeviceCodeCredentialProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public async Task<ServiceClient> CreateServiceClientAsync(
// Get token and prime the cache (may prompt user for device code auth)
await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false);

// Create ServiceClient using ConnectionOptions to ensure org metadata discovery.
// Create ServiceClient using ConnectionOptions.
// The provider function uses cached tokens and refreshes silently when needed.
var options = new ConnectionOptions
{
Expand All @@ -120,6 +120,12 @@ public async Task<ServiceClient> CreateServiceClientAsync(
};
var client = new ServiceClient(options);

// Force org metadata discovery before client is cloned by pool.
// ServiceClient uses lazy initialization - properties like ConnectedOrgFriendlyName
// are only populated when first accessed. The connection pool clones clients before
// properties are accessed, so clones would have empty metadata.
_ = client.ConnectedOrgFriendlyName;

if (!client.IsReady)
{
var error = client.LastError ?? "Unknown error";
Expand Down
13 changes: 11 additions & 2 deletions src/PPDS.Auth/Credentials/GitHubFederatedCredentialProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,24 @@ public async Task<ServiceClient> CreateServiceClientAsync(

EnsureCredentialInitialized();

// Create ServiceClient using ConnectionOptions to ensure org metadata discovery.
// The provider function acquires tokens on demand and refreshes when needed.
// Get token and prime the cache (uses cancellationToken for cancellable first request)
await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false);

// Create ServiceClient using ConnectionOptions.
// The provider function uses cached tokens and refreshes when needed.
var options = new ConnectionOptions
{
ServiceUri = new Uri(environmentUrl),
AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None)
};
var client = new ServiceClient(options);

// Force org metadata discovery before client is cloned by pool.
// ServiceClient uses lazy initialization - properties like ConnectedOrgFriendlyName
// are only populated when first accessed. The connection pool clones clients before
// properties are accessed, so clones would have empty metadata.
_ = client.ConnectedOrgFriendlyName;

if (!client.IsReady)
{
var error = client.LastError ?? "Unknown error";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public async Task<ServiceClient> CreateServiceClientAsync(
// Get token and prime the cache (may prompt user for interactive auth)
await GetTokenAsync(environmentUrl, forceInteractive, cancellationToken).ConfigureAwait(false);

// Create ServiceClient using ConnectionOptions to ensure org metadata discovery.
// Create ServiceClient using ConnectionOptions.
// The provider function uses cached tokens and refreshes silently when needed.
var options = new ConnectionOptions
{
Expand All @@ -153,6 +153,12 @@ public async Task<ServiceClient> CreateServiceClientAsync(
};
var client = new ServiceClient(options);

// Force org metadata discovery before client is cloned by pool.
// ServiceClient uses lazy initialization - properties like ConnectedOrgFriendlyName
// are only populated when first accessed. The connection pool clones clients before
// properties are accessed, so clones would have empty metadata.
_ = client.ConnectedOrgFriendlyName;

if (!client.IsReady)
{
var error = client.LastError ?? "Unknown error";
Expand Down
13 changes: 11 additions & 2 deletions src/PPDS.Auth/Credentials/ManagedIdentityCredentialProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ public async Task<ServiceClient> CreateServiceClientAsync(
// Normalize URL
environmentUrl = environmentUrl.TrimEnd('/');

// Create ServiceClient using ConnectionOptions to ensure org metadata discovery.
// The provider function acquires tokens on demand and refreshes when needed.
// Get token and prime the cache (uses cancellationToken for cancellable first request)
await GetTokenAsync(environmentUrl, cancellationToken).ConfigureAwait(false);

// Create ServiceClient using ConnectionOptions.
// The provider function uses cached tokens and refreshes when needed.
ServiceClient client;
try
{
Expand All @@ -102,6 +105,12 @@ public async Task<ServiceClient> CreateServiceClientAsync(
AccessTokenProviderFunctionAsync = _ => GetTokenAsync(environmentUrl, CancellationToken.None)
};
client = new ServiceClient(options);

// Force org metadata discovery before client is cloned by pool.
// ServiceClient uses lazy initialization - properties like ConnectedOrgFriendlyName
// are only populated when first accessed. The connection pool clones clients before
// properties are accessed, so clones would have empty metadata.
_ = client.ConnectedOrgFriendlyName;
}
catch (Exception ex)
{
Expand Down
4 changes: 2 additions & 2 deletions src/PPDS.Auth/Credentials/MsalClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@
{
cacheHelper.UnregisterCache(client.UserTokenCache);
}
catch
catch (Exception)
{
// Ignore errors during cleanup
// Cleanup should never throw - swallow all errors
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}
}
Loading
Loading