Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ public virtual async Task<T> GetConfigurationAsync(CancellationToken cancel)
return await GetConfigurationNonBlockingAsync(cancel).ConfigureAwait(false);
}

private Task UpdateCurrentConfigurationWithBypassAsync()
Comment thread
westin-m marked this conversation as resolved.
Outdated
=> UpdateCurrentConfigurationAsync(bypassCache: true);

private async Task<T> GetConfigurationNonBlockingAsync(CancellationToken cancel)
{
Exception fetchMetadataFailure = null;
Expand All @@ -231,7 +234,7 @@ private async Task<T> GetConfigurationNonBlockingAsync(CancellationToken cancel)
// If provided configuration is valid, skip regular retriaval process and update current configuration.
if (ConfigurationEventHandler != null)
{
var configurationRetrieved = await HandleBeforeRetrieveAsync(cancel).ConfigureAwait(false);
var configurationRetrieved = await HandleBeforeRetrieveAsync(new ConfigurationRetrievalContext(), cancel).ConfigureAwait(false);
Comment thread
westin-m marked this conversation as resolved.
Outdated

// replicate the behavior of successful retrieval from endpoint
if (configurationRetrieved != null && configurationRetrieved.Configuration != null)
Expand Down Expand Up @@ -308,7 +311,7 @@ private async Task<T> GetConfigurationNonBlockingAsync(CancellationToken cancel)
TelemetryConstants.Protocols.Automatic,
TelemetryConstants.Protocols.ConfigurationSourceUnknown);

_ = Task.Run(UpdateCurrentConfigurationAsync, CancellationToken.None);
_ = Task.Run(UpdateCurrentConfigurationWithBypassAsync, CancellationToken.None);
Comment thread
westin-m marked this conversation as resolved.
Outdated
Comment thread
westin-m marked this conversation as resolved.
Outdated
}
}

Expand All @@ -331,7 +334,7 @@ private async Task<T> GetConfigurationNonBlockingAsync(CancellationToken cancel)
/// The Caller should first check the state checking state using:
/// if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle).
/// </summary>
private async Task UpdateCurrentConfigurationAsync()
private async Task UpdateCurrentConfigurationAsync(bool bypassCache)
Comment thread
westin-m marked this conversation as resolved.
{
long startTimestamp = TimeProvider.GetTimestamp();

Expand All @@ -341,7 +344,8 @@ private async Task UpdateCurrentConfigurationAsync()
// If provided configuration is valid, skip regular retriaval process and update current configuration.
if (ConfigurationEventHandler != null)
{
var configurationRetrieved = await HandleBeforeRetrieveAsync().ConfigureAwait(false);
ConfigurationEventHandlerResult<T> configurationRetrieved =
await HandleBeforeRetrieveAsync(new ConfigurationRetrievalContext { BypassCache = bypassCache }).ConfigureAwait(false);
if (configurationRetrieved != null && configurationRetrieved.Configuration != null)
{
UpdateConfiguration(configurationRetrieved.Configuration, configurationRetrieved.RetrievalTime);
Expand Down Expand Up @@ -481,19 +485,30 @@ private void RequestRefreshBackgroundThread()
_isFirstRefreshRequest = false;
if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle)
{
_ = Task.Run(UpdateCurrentConfigurationAsync, CancellationToken.None);
_ = Task.Run(() => UpdateCurrentConfigurationAsync(bypassCache: true), CancellationToken.None);
Comment thread
westin-m marked this conversation as resolved.
Outdated
Comment thread
westin-m marked this conversation as resolved.
Outdated
_lastRequestRefresh = now;
}
}
}

private async Task<ConfigurationEventHandlerResult<T>> HandleBeforeRetrieveAsync(CancellationToken cancellationToken = default)
private async Task<ConfigurationEventHandlerResult<T>> HandleBeforeRetrieveAsync(ConfigurationRetrievalContext context, CancellationToken cancellationToken = default)
{
long beforeHandlerTimestamp = TimeProvider.GetTimestamp();

try
{
var handlerResult = await ConfigurationEventHandler.BeforeRetrieveAsync(MetadataAddress, cancellationToken).ConfigureAwait(false);
ConfigurationEventHandlerResult<T> handlerResult;
if (ConfigurationEventHandler is IConfigurationEventHandlerContextAware<T> contextAware)
{
handlerResult = await contextAware.BeforeRetrieveAsync(
MetadataAddress, context, cancellationToken).ConfigureAwait(false);
}
else
{
handlerResult = await ConfigurationEventHandler.BeforeRetrieveAsync(
MetadataAddress, cancellationToken).ConfigureAwait(false);
}

if (handlerResult != null && handlerResult.Configuration != null)
{
var handlerElapsedTime = TimeProvider.GetElapsedTime(beforeHandlerTimestamp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class ConfigurationManager<T> where T : class
private TimeSpan _bootstrapRefreshInterval = TimeSpan.FromSeconds(1);

/// <summary>
/// Only used to track the type of request for telemetry.
/// Used to track the type of request for signaling the event handler and for telemetry.
/// </summary>
private bool _refreshRequested;

Expand All @@ -38,7 +38,8 @@ private async Task<T> GetConfigurationWithBlockingAsync(CancellationToken cancel
// If provided configuration is valid, skip regular retriaval process and update current configuration.
if (ConfigurationEventHandler != null)
{
var configurationRetrieved = await HandleBeforeRetrieveAsync(cancel).ConfigureAwait(false);
ConfigurationEventHandlerResult<T> configurationRetrieved =
await HandleBeforeRetrieveAsync(new ConfigurationRetrievalContext { BypassCache = _refreshRequested }, cancel).ConfigureAwait(false);

// replicate the behavior of successful retrieval from endpoint
if (configurationRetrieved != null && configurationRetrieved.Configuration != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.IdentityModel.Protocols.Configuration;

/// <summary>
/// Defines the context for configuration retrieval operations.
/// </summary>
public class ConfigurationRetrievalContext
{
/// <summary>
/// Gets or sets a value indicating whether to bypass the cache when retrieving configuration.
/// </summary>
public bool BypassCache { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.IdentityModel.Protocols.Configuration;

/// <summary>
/// Defines event handlers for configuration retrieval and update operations.
/// </summary>
/// <typeparam name="T">The type of configuration.</typeparam>
public interface IConfigurationEventHandlerContextAware<T> : IConfigurationEventHandler<T> where T : class
{
/// <summary>
/// Called before retrieving configuration from the metadata endpoint.
/// </summary>
/// <param name="metadataAddress">The metadata endpoint address.</param>
/// <param name="context">The context for the configuration retrieval operation, providing additional information and control.</param>
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
/// <returns>
/// A <see cref="ConfigurationEventHandlerResult{T}"/> if valid and available, or <see cref="ConfigurationEventHandlerResult{T}.NoResult"/> to proceed with normal retrieval.
/// </returns>
Task<ConfigurationEventHandlerResult<T>> BeforeRetrieveAsync(string metadataAddress, ConfigurationRetrievalContext context, CancellationToken cancellationToken = default);

// AfterUpdateAsync does not require context, but can be added here if needed in the future.
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Previously released read/write", Scope = "member", Target = "~P:Microsoft.IdentityModel.Protocols.HttpRequestData.Headers")]
[assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Previously released read/write", Scope = "member", Target = "~P:Microsoft.IdentityModel.Protocols.HttpRequestData.PropertyBag")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Interface implementation could throw any type of an exception", Scope = "member", Target = "~M:Microsoft.IdentityModel.Protocols.ConfigurationManager`1.UpdateConfiguration(`0,System.DateTimeOffset)")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Interface implementation could throw any type of an exception", Scope = "member", Target = "~M:Microsoft.IdentityModel.Protocols.ConfigurationManager`1.HandleBeforeRetrieveAsync(System.Threading.CancellationToken)~System.Threading.Tasks.Task{Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult{`0}}")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Interface implementation could throw any type of an exception", Scope = "member", Target = "~M:Microsoft.IdentityModel.Protocols.ConfigurationManager`1.HandleBeforeRetrieveAsync(Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext,System.Threading.CancellationToken)~System.Threading.Tasks.Task{Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult{`0}}")]
#if NET6_0_OR_GREATER
[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Protocols.AuthenticationProtocolMessage.BuildRedirectUrl~System.String")]
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>.Configuration.get -> T
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>.ConfigurationEventHandlerResult(T configuration, System.DateTimeOffset retrievalTime) -> void
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>.RetrievalTime.get -> System.DateTimeOffset
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext.BypassCache.get -> bool
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext.BypassCache.set -> void
Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext.ConfigurationRetrievalContext() -> void
Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandler<T>
Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandler<T>.AfterUpdateAsync(string metadataAddress, T configuration, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandler<T>.BeforeRetrieveAsync(string metadataAddress, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>>
Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandlerContextAware<T>
Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandlerContextAware<T>.BeforeRetrieveAsync(string metadataAddress, Microsoft.IdentityModel.Protocols.Configuration.ConfigurationRetrievalContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>>
Comment thread
westin-m marked this conversation as resolved.
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.ConfigurationEventHandler.get -> Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandler<T>
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.ConfigurationEventHandler.set -> void
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.ConfigurationManager(string metadataAddress, Microsoft.IdentityModel.Protocols.IConfigurationRetriever<T> configRetriever, Microsoft.IdentityModel.Protocols.IDocumentRetriever docRetriever, Microsoft.IdentityModel.Protocols.IConfigurationValidator<T> configValidator, Microsoft.IdentityModel.Protocols.Configuration.LastKnownGoodConfigurationCacheOptions lkgCacheOptions, Microsoft.IdentityModel.Protocols.Configuration.IConfigurationEventHandler<T> configurationEventHandler) -> void
static readonly Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>.NoResult -> Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>
static readonly Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>.NoResult -> Microsoft.IdentityModel.Protocols.Configuration.ConfigurationEventHandlerResult<T>
Loading
Loading