Browse Source

Attach IServiceProvider to OpenIddict*Transaction and make OpenIddict*Dispatcher a singleton service

pull/2532/head
Kévin Chalet 2 weeks ago
parent
commit
5fc15473ce
  1. 2
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  2. 12
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  3. 33
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  4. 11
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreFeature.cs
  5. 19
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs
  6. 13
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHelpers.cs
  7. 1
      src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs
  8. 55
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs
  9. 13
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHelpers.cs
  10. 10
      src/OpenIddict.Client.Owin/OpenIddictClientOwinMiddleware.cs
  11. 12
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs
  12. 30
      src/OpenIddict.Client/IOpenIddictClientFactory.cs
  13. 89
      src/OpenIddict.Client/OpenIddictClientDispatcher.cs
  14. 10
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  15. 3
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  16. 48
      src/OpenIddict.Client/OpenIddictClientFactory.cs
  17. 254
      src/OpenIddict.Client/OpenIddictClientService.cs
  18. 49
      src/OpenIddict.Client/OpenIddictClientTransaction.cs
  19. 11
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs
  20. 19
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs
  21. 16
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs
  22. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs
  23. 54
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs
  24. 13
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs
  25. 8
      src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs
  26. 30
      src/OpenIddict.Server/IOpenIddictServerFactory.cs
  27. 89
      src/OpenIddict.Server/OpenIddictServerDispatcher.cs
  28. 10
      src/OpenIddict.Server/OpenIddictServerEvents.cs
  29. 3
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  30. 48
      src/OpenIddict.Server/OpenIddictServerFactory.cs
  31. 33
      src/OpenIddict.Server/OpenIddictServerTransaction.cs
  32. 11
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs
  33. 19
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs
  34. 17
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs
  35. 1
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs
  36. 46
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs
  37. 13
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs
  38. 12
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs
  39. 30
      src/OpenIddict.Validation/IOpenIddictValidationFactory.cs
  40. 89
      src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs
  41. 10
      src/OpenIddict.Validation/OpenIddictValidationEvents.cs
  42. 3
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  43. 48
      src/OpenIddict.Validation/OpenIddictValidationFactory.cs
  44. 53
      src/OpenIddict.Validation/OpenIddictValidationService.cs
  45. 41
      src/OpenIddict.Validation/OpenIddictValidationTransaction.cs
  46. 18
      test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs

2
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

@ -212,6 +212,7 @@ public class AuthorizationController : Controller
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new() var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{ {
ApplicationId = await _applicationManager.GetIdAsync(application), ApplicationId = await _applicationManager.GetIdAsync(application),
LoginId = result.Identity.GetClaim("login_id"),
Subject = user.Id Subject = user.Id
}); });
@ -361,6 +362,7 @@ public class AuthorizationController : Controller
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new() var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{ {
ApplicationId = await _applicationManager.GetIdAsync(application), ApplicationId = await _applicationManager.GetIdAsync(application),
LoginId = result.Identity.GetClaim("login_id"),
Subject = user.Id Subject = user.Id
}); });

12
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -501,10 +501,6 @@ This may indicate that the event handler responsible for processing OpenID Conne
<value>No service provider was found in the OWIN context. <value>No service provider was found in the OWIN context.
For the OpenIddict server services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'. For the OpenIddict server services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'.
Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictServer()' extension MUST NOT be called.</value> Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictServer()' extension MUST NOT be called.</value>
</data>
<data name="ID0122" xml:space="preserve">
<value>The authentication handler used by the OpenIddict server components cannot be resolved from the DI container.
To register the OWIN integration, use 'services.AddOpenIddict().AddServer().UseOwin()'.</value>
</data> </data>
<data name="ID0123" xml:space="preserve"> <data name="ID0123" xml:space="preserve">
<value>Audiences cannot be null or empty.</value> <value>Audiences cannot be null or empty.</value>
@ -693,10 +689,6 @@ Make sure that neither DefaultSignInScheme nor DefaultSignOutScheme point to an
<value>No service provider was found in the OWIN context. <value>No service provider was found in the OWIN context.
For the OpenIddict validation services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'. For the OpenIddict validation services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'.
Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictValidation()' extension MUST NOT be called.</value> Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictValidation()' extension MUST NOT be called.</value>
</data>
<data name="ID0169" xml:space="preserve">
<value>The authentication handler used by the OpenIddict validation components cannot be resolved from the DI container.
To register the OWIN integration, use 'services.AddOpenIddict().AddValidation().UseOwin()'.</value>
</data> </data>
<data name="ID0170" xml:space="preserve"> <data name="ID0170" xml:space="preserve">
<value>The local server integration can only be used with direct validation.</value> <value>The local server integration can only be used with direct validation.</value>
@ -1044,10 +1036,6 @@ Reference the 'OpenIddict.Client.SystemNetHttp' package and call 'services.AddOp
<value>No service provider was found in the OWIN context. <value>No service provider was found in the OWIN context.
For the OpenIddict client services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'. For the OpenIddict client services to work correctly, a per-request 'IServiceProvider' must be attached to the OWIN environment with the dictionary key 'System.IServiceProvider'.
Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictClient()' extension MUST NOT be called.</value> Note: when using a dependency injection container supporting middleware resolution (like Autofac), the 'app.UseOpenIddictClient()' extension MUST NOT be called.</value>
</data>
<data name="ID0317" xml:space="preserve">
<value>The authentication handler used by the OpenIddict client components cannot be resolved from the DI container.
To register the OWIN integration, use 'services.AddOpenIddict().AddClient().UseOwin()'.</value>
</data> </data>
<data name="ID0318" xml:space="preserve"> <data name="ID0318" xml:space="preserve">
<value>The core services must be registered when enabling the OpenIddict client feature. <value>The core services must be registered when enabling the OpenIddict client feature.

33
src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

@ -175,15 +175,10 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
null => null null => null
}; };
/// <summary> /// <inheritdoc/>
/// Determines whether the current <see cref="OpenIddictParameter"/> /// <remarks>
/// instance is equal to the specified <see cref="OpenIddictParameter"/>. /// Two instances are considered equal if they have the same representation.
/// </summary> /// </remarks>
/// <param name="other">The other object to which to compare this instance.</param>
/// <returns>
/// <see langword="true"/> if the two instances have both the same representation
/// (e.g <see cref="string"/>) and value, <see langword="false"/> otherwise.
/// </returns>
public bool Equals(OpenIddictParameter other) public bool Equals(OpenIddictParameter other)
{ {
return (_value, other._value) switch return (_value, other._value) switch
@ -282,22 +277,14 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
}; };
} }
/// <summary> /// <inheritdoc/>
/// Determines whether the current <see cref="OpenIddictParameter"/> /// <remarks>
/// instance is equal to the specified <see cref="object"/>. /// Two instances are considered equal if they have the same representation.
/// </summary> /// </remarks>
/// <param name="obj">The other object to which to compare this instance.</param>
/// <returns>
/// <see langword="true"/> if the two instances have both the same representation
/// (e.g <see cref="string"/>) and value, <see langword="false"/> otherwise.
/// </returns>
public override bool Equals([NotNullWhen(true)] object? obj) public override bool Equals([NotNullWhen(true)] object? obj)
=> obj is OpenIddictParameter parameter && Equals(parameter); => obj is OpenIddictParameter parameter && Equals(parameter);
/// <summary> /// <inheritdoc/>
/// Returns the hash code of the current <see cref="OpenIddictParameter"/> instance.
/// </summary>
/// <returns>The hash code for the current instance.</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return _value switch return _value switch
@ -1010,7 +997,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
long value => value.ToString(CultureInfo.InvariantCulture), long value => value.ToString(CultureInfo.InvariantCulture),
// When the parameter is a JSON boolean value, use its string representation. // When the parameter is a JSON boolean value, use its string representation.
JsonElement { ValueKind: JsonValueKind.True } => "true", JsonElement { ValueKind: JsonValueKind.True } => "true",
JsonElement { ValueKind: JsonValueKind.False } => "false", JsonElement { ValueKind: JsonValueKind.False } => "false",
// When the parameter is a JsonElement, try to convert it if it's of a supported type. // When the parameter is a JsonElement, try to convert it if it's of a supported type.

11
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreFeature.cs

@ -9,14 +9,17 @@ using System.ComponentModel;
namespace OpenIddict.Client.AspNetCore; namespace OpenIddict.Client.AspNetCore;
/// <summary> /// <summary>
/// Exposes the current client transaction to the ASP.NET Core host. /// Exposes the current client transaction to the ASP.NET Core application.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientAspNetCoreFeature public sealed class OpenIddictClientAspNetCoreFeature
{ {
/// <summary> /// <summary>
/// Gets or sets the client transaction that encapsulates all specific /// Gets the transaction that encapsulates all specific information about an individual operation.
/// information about an individual OpenID Connect client request.
/// </summary> /// </summary>
public OpenIddictClientTransaction? Transaction { get; set; } public required OpenIddictClientTransaction Transaction
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

19
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using static OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreConstants; using static OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreConstants;
@ -24,22 +25,17 @@ public sealed class OpenIddictClientAspNetCoreHandler : AuthenticationHandler<Au
IAuthenticationSignOutHandler IAuthenticationSignOutHandler
{ {
private readonly IOpenIddictClientDispatcher _dispatcher; private readonly IOpenIddictClientDispatcher _dispatcher;
private readonly IOpenIddictClientFactory _factory;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientAspNetCoreHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictClientAspNetCoreHandler"/> class.
/// </summary> /// </summary>
public OpenIddictClientAspNetCoreHandler( public OpenIddictClientAspNetCoreHandler(
IOpenIddictClientDispatcher dispatcher, IOpenIddictClientDispatcher dispatcher,
IOpenIddictClientFactory factory,
IOptionsMonitor<AuthenticationSchemeOptions> options, IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder) UrlEncoder encoder)
: base(options, logger, encoder) : base(options, logger, encoder)
{ => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> HandleRequestAsync() public async Task<bool> HandleRequestAsync()
@ -59,9 +55,16 @@ public sealed class OpenIddictClientAspNetCoreHandler : AuthenticationHandler<Au
var transaction = Context.Features.Get<OpenIddictClientAspNetCoreFeature>()?.Transaction; var transaction = Context.Features.Get<OpenIddictClientAspNetCoreFeature>()?.Transaction;
if (transaction is null) if (transaction is null)
{ {
var options = Context.RequestServices.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
// Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictClientTransaction
transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference<HttpRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(HttpRequest).FullName!] = Request },
ServiceProvider = Context.RequestServices
};
// Attach the OpenIddict client transaction to the ASP.NET Core features // Attach the OpenIddict client transaction to the ASP.NET Core features
// so that it can retrieved while performing sign-in/sign-out operations. // so that it can retrieved while performing sign-in/sign-out operations.

13
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHelpers.cs

@ -23,17 +23,8 @@ public static class OpenIddictClientAspNetCoreHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)
{ && property is HttpRequest request ? request : null;
return null;
}
if (property is WeakReference<HttpRequest> reference && reference.TryGetTarget(out HttpRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>

1
src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs

@ -31,7 +31,6 @@ public static class OpenIddictClientOwinExtensions
// Note: unlike regular OWIN middleware, the OpenIddict client middleware is registered // Note: unlike regular OWIN middleware, the OpenIddict client middleware is registered
// as a scoped service in the DI container. This allows containers that support middleware // as a scoped service in the DI container. This allows containers that support middleware
// resolution (like Autofac) to use it without requiring additional configuration. // resolution (like Autofac) to use it without requiring additional configuration.
builder.Services.TryAddScoped<OpenIddictClientOwinHandler>();
builder.Services.TryAddScoped<OpenIddictClientOwinMiddleware>(); builder.Services.TryAddScoped<OpenIddictClientOwinMiddleware>();
// Register the built-in event handlers used by the OpenIddict OWIN client components. // Register the built-in event handlers used by the OpenIddict OWIN client components.

55
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs

@ -10,6 +10,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Claims; using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Owin.Security.Infrastructure; using Microsoft.Owin.Security.Infrastructure;
using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants;
@ -23,29 +24,20 @@ namespace OpenIddict.Client.Owin;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<AuthenticationOptions> public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<AuthenticationOptions>
{ {
private readonly IOpenIddictClientDispatcher _dispatcher; private readonly IServiceProvider _provider;
private readonly IOpenIddictClientFactory _factory;
private readonly IOptionsMonitor<OpenIddictClientOwinOptions> _options;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientOwinHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictClientOwinHandler"/> class.
/// </summary> /// </summary>
/// <param name="dispatcher">The OpenIddict client dispatcher used by this instance.</param> /// <param name="provider">The service provider.</param>
/// <param name="factory">The OpenIddict client factory used by this instance.</param> public OpenIddictClientOwinHandler(IServiceProvider provider)
/// <param name="options">The OpenIddict client OWIN options.</param> => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
public OpenIddictClientOwinHandler(
IOpenIddictClientDispatcher dispatcher,
IOpenIddictClientFactory factory,
IOptionsMonitor<OpenIddictClientOwinOptions> options)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task InitializeCoreAsync() protected override async Task InitializeCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictClientDispatcher>();
// Note: to ensure internal operations are not immediately cancelled when the request is aborted // Note: to ensure internal operations are not immediately cancelled when the request is aborted
// (which may represent a security risk if sensitive operations are in progress), an ad-hoc token // (which may represent a security risk if sensitive operations are in progress), an ad-hoc token
// source is always created and configured to be triggered 5 seconds after the request is aborted. // source is always created and configured to be triggered 5 seconds after the request is aborted.
@ -61,9 +53,16 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName); var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName);
if (transaction is null) if (transaction is null)
{ {
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
// Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictClientTransaction
transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference<IOwinRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(IOwinRequest).FullName!] = Request },
ServiceProvider = _provider
};
// Attach the OpenIddict client transaction to the OWIN shared dictionary // Attach the OpenIddict client transaction to the OWIN shared dictionary
// so that it can retrieved while performing sign-in/sign-out operations. // so that it can retrieved while performing sign-in/sign-out operations.
@ -71,7 +70,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
} }
var context = new ProcessRequestContext(transaction); var context = new ProcessRequestContext(transaction);
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
// Store the context in the transaction so that it can be retrieved from InvokeAsync(). // Store the context in the transaction so that it can be retrieved from InvokeAsync().
transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context);
@ -84,6 +83,8 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
// in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when
// active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case.
var dispatcher = _provider.GetRequiredService<IOpenIddictClientDispatcher>();
var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName) var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0315)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0315));
@ -110,7 +111,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled) if (notification.IsRequestHandled)
{ {
@ -131,6 +132,8 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync() protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictClientDispatcher>();
var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName) var transaction = Context.Get<OpenIddictClientTransaction>(typeof(OpenIddictClientTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0315)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0315));
@ -140,7 +143,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!); var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!);
if (context is null) if (context is null)
{ {
await _dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction)); await dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction));
// Store the context object in the transaction so it can be later retrieved by handlers // Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow. // that want to access the authentication result without triggering a new authentication flow.
@ -287,7 +290,9 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
// OpenIddictClientOwinMiddleware is assumed to be the only middleware allowed to write // OpenIddictClientOwinMiddleware is assumed to be the only middleware allowed to write
// to the response stream when a response grant (sign-in/out or challenge) was applied. // to the response stream when a response grant (sign-in/out or challenge) was applied.
var descriptions = _options.CurrentValue.ForwardedAuthenticationTypes; var dispatcher = _provider.GetRequiredService<IOpenIddictClientDispatcher>();
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOwinOptions>>();
var descriptions = options.CurrentValue.ForwardedAuthenticationTypes;
// Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code
// corresponds to a challenge response, as LookupChallenge() will always return a non-null // corresponds to a challenge response, as LookupChallenge() will always return a non-null
@ -308,7 +313,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
Request = new OpenIddictRequest() Request = new OpenIddictRequest()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -325,7 +330,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -352,7 +357,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
Request = new OpenIddictRequest() Request = new OpenIddictRequest()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -369,7 +374,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {

13
src/OpenIddict.Client.Owin/OpenIddictClientOwinHelpers.cs

@ -37,17 +37,8 @@ public static class OpenIddictClientOwinHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)
{ && property is IOwinRequest request ? request : null;
return null;
}
if (property is WeakReference<IOwinRequest> reference && reference.TryGetTarget(out IOwinRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>

10
src/OpenIddict.Client.Owin/OpenIddictClientOwinMiddleware.cs

@ -28,10 +28,12 @@ using AuthenticateDelegate = Func<
/// <summary> /// <summary>
/// Provides the entry point necessary to register the OpenIddict client handler in an OWIN pipeline. /// Provides the entry point necessary to register the OpenIddict client handler in an OWIN pipeline.
/// </summary>
/// <remarks>
/// Note: this middleware is intended to be used with dependency injection containers /// Note: this middleware is intended to be used with dependency injection containers
/// that support middleware resolution, like Autofac. Since it depends on scoped services, /// that support middleware resolution, like Autofac. Since it depends on scoped services,
/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. /// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware.
/// </summary> /// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions> public sealed class OpenIddictClientOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions>
{ {
@ -54,8 +56,7 @@ public sealed class OpenIddictClientOwinMiddleware : AuthenticationMiddleware<Au
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
// Resolve the list of forwarded authentication types from the options. // Resolve the list of forwarded authentication types from the options.
var options = _provider.GetService<IOptionsMonitor<OpenIddictClientOwinOptions>>() var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOwinOptions>>().CurrentValue;
?.CurrentValue ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0316));
// Retrieve the existing authentication delegate. // Retrieve the existing authentication delegate.
var function = context.Get<AuthenticateDelegate?>("security.Authenticate"); var function = context.Get<AuthenticateDelegate?>("security.Authenticate");
@ -154,8 +155,7 @@ public sealed class OpenIddictClientOwinMiddleware : AuthenticationMiddleware<Au
/// </summary> /// </summary>
/// <returns>A new instance of the <see cref="OpenIddictClientOwinHandler"/> class.</returns> /// <returns>A new instance of the <see cref="OpenIddictClientOwinHandler"/> class.</returns>
protected override AuthenticationHandler<AuthenticationOptions> CreateHandler() protected override AuthenticationHandler<AuthenticationOptions> CreateHandler()
=> _provider.GetService<OpenIddictClientOwinHandler>() => new OpenIddictClientOwinHandler(_provider);
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0317));
/// <summary> /// <summary>
/// Provides the options used by the <see cref="OpenIddictClientOwinMiddleware"/> class. /// Provides the options used by the <see cref="OpenIddictClientOwinMiddleware"/> class.

12
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationService.cs

@ -13,7 +13,6 @@ using Microsoft.Extensions.Options;
#if ANDROID #if ANDROID
using Android.Content; using Android.Content;
using OpenIddict.Extensions;
#endif #endif
namespace OpenIddict.Client.SystemIntegration; namespace OpenIddict.Client.SystemIntegration;
@ -143,12 +142,17 @@ public sealed class OpenIddictClientSystemIntegrationService
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
// Create a client transaction and store the specified instance so // Create a client transaction and store the specified instance so
// it can be retrieved by the event handlers that need to access it. // it can be retrieved by the event handlers that need to access it.
var transaction = await factory.CreateTransactionAsync(cancellationToken); var transaction = new OpenIddictClientTransaction
transaction.SetProperty(typeof(TProperty).FullName!, property); {
CancellationToken = cancellationToken,
Options = options.CurrentValue,
Properties = { [typeof(TProperty).FullName!] = property },
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessRequestContext(transaction); var context = new ProcessRequestContext(transaction);
await dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);

30
src/OpenIddict.Client/IOpenIddictClientFactory.cs

@ -1,30 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
namespace OpenIddict.Client;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IOpenIddictClientFactory
{
/// <summary>
/// Creates a new <see cref="OpenIddictClientTransaction"/> that is used as a
/// way to store per-request data needed to process the requested operation.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>
/// Note: the specified <see cref="CancellationToken"/> is automatically attached to the returned transaction.
/// </remarks>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous
/// operation, whose result returns the created transaction.
/// </returns>
ValueTask<OpenIddictClientTransaction> CreateTransactionAsync(CancellationToken cancellationToken);
}

89
src/OpenIddict.Client/OpenIddictClientDispatcher.cs

@ -6,7 +6,6 @@
using System.ComponentModel; using System.ComponentModel;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Client; namespace OpenIddict.Client;
@ -16,113 +15,73 @@ namespace OpenIddict.Client;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientDispatcher : IOpenIddictClientDispatcher public sealed class OpenIddictClientDispatcher : IOpenIddictClientDispatcher
{ {
private readonly ILogger<OpenIddictClientDispatcher> _logger;
private readonly IOptionsMonitor<OpenIddictClientOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientDispatcher"/> class.
/// </summary>
public OpenIddictClientDispatcher(
ILogger<OpenIddictClientDispatcher> logger,
IOptionsMonitor<OpenIddictClientOptions> options,
IServiceProvider provider)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
await foreach (var handler in GetHandlersAsync()) // Note: the descriptors collection is sorted during options initialization for performance reasons.
foreach (var descriptor in context.Options.Handlers)
{ {
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
var handler = descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictClientHandler<TContext>
?? context.ServiceProvider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictClientHandler<TContext>
?? throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType));
try try
{ {
await handler.HandleAsync(context); await handler.HandleAsync(context);
} }
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); context.Logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName);
throw; throw;
} }
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName);
} }
switch (context) switch (context)
{ {
case BaseRequestContext { IsRequestHandled: true }: case BaseRequestContext { IsRequestHandled: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseRequestContext { IsRequestSkipped: true }: case BaseRequestContext { IsRequestSkipped: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseValidatingContext { IsRejected: true }: case BaseValidatingContext { IsRejected: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
default: continue;
}
}
async IAsyncEnumerable<IOpenIddictClientHandler<TContext>> GetHandlersAsync()
{
// Note: the descriptors collection is sorted during options initialization for performance reasons.
var descriptors = _options.CurrentValue.Handlers;
if (descriptors.Count is 0)
{
yield break;
}
for (var index = 0; index < descriptors.Count; index++)
{
var descriptor = descriptors[index];
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
yield return descriptor.ServiceDescriptor switch
{
{ ImplementationInstance: IOpenIddictClientHandler<TContext> handler } => handler,
_ when _provider.GetService(descriptor.ServiceDescriptor.ServiceType)
is IOpenIddictClientHandler<TContext> handler => handler,
_ => throw new InvalidOperationException(SR.FormatID0312(descriptor.ServiceDescriptor.ServiceType))
};
} }
} }
async ValueTask<bool> IsActiveAsync(OpenIddictClientHandlerDescriptor descriptor) async ValueTask<bool> IsActiveAsync(OpenIddictClientHandlerDescriptor descriptor)
{ {
for (var index = 0; index < descriptor.FilterTypes.Length; index++) foreach (var type in descriptor.FilterTypes)
{ {
if (_provider.GetService(descriptor.FilterTypes[index]) is not IOpenIddictClientHandlerFilter<TContext> filter) var filter = context.ServiceProvider.GetService(type) as IOpenIddictClientHandlerFilter<TContext>
{ ?? throw new InvalidOperationException(SR.FormatID0099(type));
throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index]));
}
if (!await filter.IsActiveAsync(context)) if (!await filter.IsActiveAsync(context))
{ {

10
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace OpenIddict.Client; namespace OpenIddict.Client;
@ -66,7 +67,8 @@ public static partial class OpenIddictClientEvents
/// <summary> /// <summary>
/// Gets the logger responsible for logging processed operations. /// Gets the logger responsible for logging processed operations.
/// </summary> /// </summary>
public ILogger Logger => Transaction.Logger; public ILogger Logger
=> field ??= Transaction.ServiceProvider.GetRequiredService<ILogger<OpenIddictClientDispatcher>>();
/// <summary> /// <summary>
/// Gets the OpenIddict client options. /// Gets the OpenIddict client options.
@ -90,6 +92,12 @@ public static partial class OpenIddictClientEvents
get => Transaction.Registration; get => Transaction.Registration;
set => Transaction.Registration = value; set => Transaction.Registration = value;
} }
/// <summary>
/// Gets the service provider associated with the current transaction.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IServiceProvider ServiceProvider => Transaction.ServiceProvider;
} }
/// <summary> /// <summary>

3
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -28,8 +28,7 @@ public static class OpenIddictClientExtensions
builder.Services.AddLogging(); builder.Services.AddLogging();
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.TryAddScoped<IOpenIddictClientDispatcher, OpenIddictClientDispatcher>(); builder.Services.TryAddSingleton<IOpenIddictClientDispatcher, OpenIddictClientDispatcher>();
builder.Services.TryAddScoped<IOpenIddictClientFactory, OpenIddictClientFactory>();
builder.Services.TryAddSingleton<OpenIddictClientService>(); builder.Services.TryAddSingleton<OpenIddictClientService>();
// Register the built-in filters used by the default OpenIddict client event handlers. // Register the built-in filters used by the default OpenIddict client event handlers.

48
src/OpenIddict.Client/OpenIddictClientFactory.cs

@ -1,48 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Client;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictClientFactory : IOpenIddictClientFactory
{
private readonly ILogger<OpenIddictClientDispatcher> _logger;
private readonly IOptionsMonitor<OpenIddictClientOptions> _options;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientFactory"/> class.
/// </summary>
public OpenIddictClientFactory(
ILogger<OpenIddictClientDispatcher> logger,
IOptionsMonitor<OpenIddictClientOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc/>
public ValueTask<OpenIddictClientTransaction> CreateTransactionAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new(Task.FromCanceled<OpenIddictClientTransaction>(cancellationToken));
}
return new(new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Logger = _logger,
Options = _options.CurrentValue
});
}
}

254
src/OpenIddict.Client/OpenIddictClientService.cs

@ -258,15 +258,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken); var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -328,15 +330,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken); var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessChallengeContext(transaction) var context = new ProcessChallengeContext(transaction)
{ {
@ -410,14 +414,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -500,14 +507,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -592,16 +602,17 @@ public class OpenIddictClientService
try try
{ {
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken); var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -704,15 +715,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken); var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessChallengeContext(transaction) var context = new ProcessChallengeContext(transaction)
{ {
@ -783,14 +796,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -870,14 +886,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -957,14 +976,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -1042,14 +1064,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessIntrospectionContext(transaction) var context = new ProcessIntrospectionContext(transaction)
{ {
@ -1101,14 +1126,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessRevocationContext(transaction) var context = new ProcessRevocationContext(transaction)
{ {
@ -1168,14 +1196,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var request = new OpenIddictRequest(); var request = new OpenIddictRequest();
request = await PrepareConfigurationRequestAsync(); request = await PrepareConfigurationRequestAsync();
@ -1288,15 +1319,17 @@ public class OpenIddictClientService
request.CancellationToken.ThrowIfCancellationRequested(); request.CancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(request.CancellationToken); var transaction = new OpenIddictClientTransaction
{
CancellationToken = request.CancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var context = new ProcessSignOutContext(transaction) var context = new ProcessSignOutContext(transaction)
{ {
@ -1359,14 +1392,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
var request = new OpenIddictRequest(); var request = new OpenIddictRequest();
request = await PrepareJsonWebKeySetRequestAsync(); request = await PrepareJsonWebKeySetRequestAsync();
@ -1497,14 +1533,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PrepareDeviceAuthorizationRequestAsync(); request = await PrepareDeviceAuthorizationRequestAsync();
request = await ApplyDeviceAuthorizationRequestAsync(); request = await ApplyDeviceAuthorizationRequestAsync();
@ -1638,14 +1677,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PrepareIntrospectionRequestAsync(); request = await PrepareIntrospectionRequestAsync();
request = await ApplyIntrospectionRequestAsync(); request = await ApplyIntrospectionRequestAsync();
@ -1782,14 +1824,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PreparePushedAuthorizationRequestAsync(); request = await PreparePushedAuthorizationRequestAsync();
request = await ApplyPushedAuthorizationRequestAsync(); request = await ApplyPushedAuthorizationRequestAsync();
@ -1923,14 +1968,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PrepareRevocationRequestAsync(); request = await PrepareRevocationRequestAsync();
request = await ApplyRevocationRequestAsync(); request = await ApplyRevocationRequestAsync();
@ -2065,14 +2113,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PrepareTokenRequestAsync(); request = await PrepareTokenRequestAsync();
request = await ApplyTokenRequestAsync(); request = await ApplyTokenRequestAsync();
@ -2208,14 +2259,17 @@ public class OpenIddictClientService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictClientTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = scope.ServiceProvider
};
request = await PrepareUserInfoRequestAsync(); request = await PrepareUserInfoRequestAsync();
request = await ApplyUserInfoRequestAsync(); request = await ApplyUserInfoRequestAsync();

49
src/OpenIddict.Client/OpenIddictClientTransaction.cs

@ -5,23 +5,22 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Client; namespace OpenIddict.Client;
/// <summary> /// <summary>
/// Represents the context associated with an OpenID Connect client request. /// Represents the context associated with an OpenID Connect client operation.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientTransaction public sealed class OpenIddictClientTransaction
{ {
/// <summary> /// <summary>
/// Gets or sets the cancellation token used to determine if the operation was aborted. /// Gets the cancellation token used to determine if the operation was aborted.
/// </summary> /// </summary>
public CancellationToken CancellationToken { get; set; } public required CancellationToken CancellationToken { get; init; }
/// <summary> /// <summary>
/// Gets or sets the type of the endpoint processing the current request. /// Gets or sets the type of the endpoint processing the current transaction.
/// </summary> /// </summary>
public OpenIddictClientEndpointType EndpointType { get; set; } public OpenIddictClientEndpointType EndpointType { get; set; }
@ -36,29 +35,36 @@ public sealed class OpenIddictClientTransaction
public Uri? BaseUri { get; set; } public Uri? BaseUri { get; set; }
/// <summary> /// <summary>
/// Gets or sets the logger associated with the current request. /// Gets the options associated with the current transaction.
/// </summary> /// </summary>
public ILogger Logger { get; set; } = default!; public required OpenIddictClientOptions Options
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
/// <summary> /// <summary>
/// Gets or sets the options associated with the current request. /// Gets the additional properties associated with the current transaction.
/// </summary>
public OpenIddictClientOptions Options { get; set; } = default!;
/// <summary>
/// Gets the additional properties associated with the current request.
/// </summary> /// </summary>
public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Gets or sets the client registration used for the current request. /// Gets or sets the client registration used for the current transaction.
/// </summary> /// </summary>
public OpenIddictClientRegistration Registration { get; set; } = default!; public OpenIddictClientRegistration Registration
{
get;
set { ArgumentNullException.ThrowIfNull(value); field = value; }
} = default!;
/// <summary> /// <summary>
/// Gets or sets the server configuration used for the current request. /// Gets or sets the server configuration used for the current transaction.
/// </summary> /// </summary>
public OpenIddictConfiguration Configuration { get; set; } = default!; public OpenIddictConfiguration Configuration
{
get;
set { ArgumentNullException.ThrowIfNull(value); field = value; }
} = default!;
/// <summary> /// <summary>
/// Gets or sets the current OpenID Connect request. /// Gets or sets the current OpenID Connect request.
@ -69,4 +75,13 @@ public sealed class OpenIddictClientTransaction
/// Gets or sets the current OpenID Connect response being returned. /// Gets or sets the current OpenID Connect response being returned.
/// </summary> /// </summary>
public OpenIddictResponse? Response { get; set; } public OpenIddictResponse? Response { get; set; }
/// <summary>
/// Gets the service provider used to resolve services.
/// </summary>
public required IServiceProvider ServiceProvider
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

11
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs

@ -9,14 +9,17 @@ using System.ComponentModel;
namespace OpenIddict.Server.AspNetCore; namespace OpenIddict.Server.AspNetCore;
/// <summary> /// <summary>
/// Exposes the current server transaction to the ASP.NET Core host. /// Exposes the current server transaction to the ASP.NET Core application.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerAspNetCoreFeature public sealed class OpenIddictServerAspNetCoreFeature
{ {
/// <summary> /// <summary>
/// Gets or sets the server transaction that encapsulates all specific /// Gets the transaction that encapsulates all specific information about an individual operation.
/// information about an individual OpenID Connect server request.
/// </summary> /// </summary>
public OpenIddictServerTransaction? Transaction { get; set; } public required OpenIddictServerTransaction Transaction
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

19
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs

@ -7,6 +7,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants;
@ -24,22 +25,17 @@ public sealed class OpenIddictServerAspNetCoreHandler : AuthenticationHandler<Au
IAuthenticationSignOutHandler IAuthenticationSignOutHandler
{ {
private readonly IOpenIddictServerDispatcher _dispatcher; private readonly IOpenIddictServerDispatcher _dispatcher;
private readonly IOpenIddictServerFactory _factory;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerAspNetCoreHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictServerAspNetCoreHandler"/> class.
/// </summary> /// </summary>
public OpenIddictServerAspNetCoreHandler( public OpenIddictServerAspNetCoreHandler(
IOpenIddictServerDispatcher dispatcher, IOpenIddictServerDispatcher dispatcher,
IOpenIddictServerFactory factory,
IOptionsMonitor<AuthenticationSchemeOptions> options, IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder) UrlEncoder encoder)
: base(options, logger, encoder) : base(options, logger, encoder)
{ => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> HandleRequestAsync() public async Task<bool> HandleRequestAsync()
@ -59,9 +55,16 @@ public sealed class OpenIddictServerAspNetCoreHandler : AuthenticationHandler<Au
var transaction = Context.Features.Get<OpenIddictServerAspNetCoreFeature>()?.Transaction; var transaction = Context.Features.Get<OpenIddictServerAspNetCoreFeature>()?.Transaction;
if (transaction is null) if (transaction is null)
{ {
var options = Context.RequestServices.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>();
// Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictServerTransaction
transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference<HttpRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(HttpRequest).FullName!] = Request },
ServiceProvider = Context.RequestServices
};
// Attach the OpenIddict server transaction to the ASP.NET Core features // Attach the OpenIddict server transaction to the ASP.NET Core features
// so that it can retrieved while performing sign-in/sign-out operations. // so that it can retrieved while performing sign-in/sign-out operations.

16
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs

@ -23,17 +23,9 @@ public static class OpenIddictServerAspNetCoreHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)
{ && property is HttpRequest request ? request : null;
return null;
}
if (property is WeakReference<HttpRequest> reference && reference.TryGetTarget(out HttpRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>
@ -52,7 +44,7 @@ public static class OpenIddictServerAspNetCoreHelpers
/// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>. /// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>.
/// </summary> /// </summary>
/// <param name="context">The context instance.</param> /// <param name="context">The context instance.</param>
/// <returns>The <see cref="OpenIddictRequest"/> instance or <c>null</c> if it couldn't be found.</returns> /// <returns>The <see cref="OpenIddictRequest"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static OpenIddictRequest? GetOpenIddictServerRequest(this HttpContext context) public static OpenIddictRequest? GetOpenIddictServerRequest(this HttpContext context)
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
@ -64,7 +56,7 @@ public static class OpenIddictServerAspNetCoreHelpers
/// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>. /// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>.
/// </summary> /// </summary>
/// <param name="context">The context instance.</param> /// <param name="context">The context instance.</param>
/// <returns>The <see cref="OpenIddictResponse"/> instance or <c>null</c> if it couldn't be found.</returns> /// <returns>The <see cref="OpenIddictResponse"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static OpenIddictResponse? GetOpenIddictServerResponse(this HttpContext context) public static OpenIddictResponse? GetOpenIddictServerResponse(this HttpContext context)
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs

@ -31,7 +31,6 @@ public static class OpenIddictServerOwinExtensions
// Note: unlike regular OWIN middleware, the OpenIddict server middleware is registered // Note: unlike regular OWIN middleware, the OpenIddict server middleware is registered
// as a scoped service in the DI container. This allows containers that support middleware // as a scoped service in the DI container. This allows containers that support middleware
// resolution (like Autofac) to use it without requiring additional configuration. // resolution (like Autofac) to use it without requiring additional configuration.
builder.Services.TryAddScoped<OpenIddictServerOwinHandler>();
builder.Services.TryAddScoped<OpenIddictServerOwinMiddleware>(); builder.Services.TryAddScoped<OpenIddictServerOwinMiddleware>();
// Register the built-in event handlers used by the OpenIddict OWIN server components. // Register the built-in event handlers used by the OpenIddict OWIN server components.

54
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs

@ -6,6 +6,8 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Security.Infrastructure; using Microsoft.Owin.Security.Infrastructure;
using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants;
using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties; using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties;
@ -18,25 +20,20 @@ namespace OpenIddict.Server.Owin;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<AuthenticationOptions> public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<AuthenticationOptions>
{ {
private readonly IOpenIddictServerDispatcher _dispatcher; private readonly IServiceProvider _provider;
private readonly IOpenIddictServerFactory _factory;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerOwinHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictServerOwinHandler"/> class.
/// </summary> /// </summary>
/// <param name="dispatcher">The OpenIddict server dispatcher used by this instance.</param> /// <param name="provider">The service provider.</param>
/// <param name="factory">The OpenIddict server factory used by this instance.</param> public OpenIddictServerOwinHandler(IServiceProvider provider)
public OpenIddictServerOwinHandler( => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
IOpenIddictServerDispatcher dispatcher,
IOpenIddictServerFactory factory)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task InitializeCoreAsync() protected override async Task InitializeCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictServerDispatcher>();
// Note: to ensure internal operations are not immediately cancelled when the request is aborted // Note: to ensure internal operations are not immediately cancelled when the request is aborted
// (which may represent a security risk if sensitive operations are in progress), an ad-hoc token // (which may represent a security risk if sensitive operations are in progress), an ad-hoc token
// source is always created and configured to be triggered 5 seconds after the request is aborted. // source is always created and configured to be triggered 5 seconds after the request is aborted.
@ -52,9 +49,16 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName); var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName);
if (transaction is null) if (transaction is null)
{ {
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>();
// Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictServerTransaction
transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference<IOwinRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(IOwinRequest).FullName!] = Request },
ServiceProvider = _provider
};
// Attach the OpenIddict server transaction to the OWIN shared dictionary // Attach the OpenIddict server transaction to the OWIN shared dictionary
// so that it can retrieved while performing sign-in/sign-out operations. // so that it can retrieved while performing sign-in/sign-out operations.
@ -62,7 +66,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
} }
var context = new ProcessRequestContext(transaction); var context = new ProcessRequestContext(transaction);
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
// Store the context in the transaction so that it can be retrieved from InvokeAsync(). // Store the context in the transaction so that it can be retrieved from InvokeAsync().
transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context);
@ -75,6 +79,8 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
// in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when
// active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case.
var dispatcher = _provider.GetRequiredService<IOpenIddictServerDispatcher>();
var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName) var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0112));
@ -101,7 +107,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled) if (notification.IsRequestHandled)
{ {
@ -122,6 +128,8 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync() protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictServerDispatcher>();
var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName) var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0112));
@ -131,7 +139,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!); var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!);
if (context is null) if (context is null)
{ {
await _dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction)); await dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction));
// Store the context object in the transaction so it can be later retrieved by handlers // Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow. // that want to access the authentication result without triggering a new authentication flow.
@ -279,6 +287,8 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
// OpenIddictServerOwinMiddleware is assumed to be the only middleware allowed to write // OpenIddictServerOwinMiddleware is assumed to be the only middleware allowed to write
// to the response stream when a response grant (sign-in/out or challenge) was applied. // to the response stream when a response grant (sign-in/out or challenge) was applied.
var dispatcher = _provider.GetRequiredService<IOpenIddictServerDispatcher>();
// Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code
// corresponds to a challenge response, as LookupChallenge() will always return a non-null // corresponds to a challenge response, as LookupChallenge() will always return a non-null
// value when active authentication is used, even if no challenge was actually triggered. // value when active authentication is used, even if no challenge was actually triggered.
@ -295,7 +305,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -312,7 +322,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -337,7 +347,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -354,7 +364,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -378,7 +388,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -395,7 +405,7 @@ public sealed class OpenIddictServerOwinHandler : AuthenticationHandler<Authenti
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {

13
src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs

@ -37,17 +37,8 @@ public static class OpenIddictServerOwinHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)
{ && property is IOwinRequest request ? request : null;
return null;
}
if (property is WeakReference<IOwinRequest> reference && reference.TryGetTarget(out IOwinRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>

8
src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs

@ -5,17 +5,18 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Owin.Security.Infrastructure; using Microsoft.Owin.Security.Infrastructure;
namespace OpenIddict.Server.Owin; namespace OpenIddict.Server.Owin;
/// <summary> /// <summary>
/// Provides the entry point necessary to register the OpenIddict server handler in an OWIN pipeline. /// Provides the entry point necessary to register the OpenIddict server handler in an OWIN pipeline.
/// </summary>
/// <remarks>
/// Note: this middleware is intended to be used with dependency injection containers /// Note: this middleware is intended to be used with dependency injection containers
/// that support middleware resolution, like Autofac. Since it depends on scoped services, /// that support middleware resolution, like Autofac. Since it depends on scoped services,
/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. /// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware.
/// </summary> /// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions> public sealed class OpenIddictServerOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions>
{ {
@ -37,8 +38,7 @@ public sealed class OpenIddictServerOwinMiddleware : AuthenticationMiddleware<Au
/// </summary> /// </summary>
/// <returns>A new instance of the <see cref="OpenIddictServerOwinHandler"/> class.</returns> /// <returns>A new instance of the <see cref="OpenIddictServerOwinHandler"/> class.</returns>
protected override AuthenticationHandler<AuthenticationOptions> CreateHandler() protected override AuthenticationHandler<AuthenticationOptions> CreateHandler()
=> _provider.GetService<OpenIddictServerOwinHandler>() => new OpenIddictServerOwinHandler(_provider);
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0122));
/// <summary> /// <summary>
/// Provides the options used by the <see cref="OpenIddictServerOwinMiddleware"/> class. /// Provides the options used by the <see cref="OpenIddictServerOwinMiddleware"/> class.

30
src/OpenIddict.Server/IOpenIddictServerFactory.cs

@ -1,30 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
namespace OpenIddict.Server;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IOpenIddictServerFactory
{
/// <summary>
/// Creates a new <see cref="OpenIddictServerTransaction"/> that is used as a
/// way to store per-request data needed to process the requested operation.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>
/// Note: the specified <see cref="CancellationToken"/> is automatically attached to the returned transaction.
/// </remarks>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous
/// operation, whose result returns the created transaction.
/// </returns>
ValueTask<OpenIddictServerTransaction> CreateTransactionAsync(CancellationToken cancellationToken);
}

89
src/OpenIddict.Server/OpenIddictServerDispatcher.cs

@ -6,7 +6,6 @@
using System.ComponentModel; using System.ComponentModel;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Server; namespace OpenIddict.Server;
@ -16,113 +15,73 @@ namespace OpenIddict.Server;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerDispatcher : IOpenIddictServerDispatcher public sealed class OpenIddictServerDispatcher : IOpenIddictServerDispatcher
{ {
private readonly ILogger<OpenIddictServerDispatcher> _logger;
private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerDispatcher"/> class.
/// </summary>
public OpenIddictServerDispatcher(
ILogger<OpenIddictServerDispatcher> logger,
IOptionsMonitor<OpenIddictServerOptions> options,
IServiceProvider provider)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
await foreach (var handler in GetHandlersAsync()) // Note: the descriptors collection is sorted during options initialization for performance reasons.
foreach (var descriptor in context.Options.Handlers)
{ {
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
var handler = descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictServerHandler<TContext>
?? context.ServiceProvider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictServerHandler<TContext>
?? throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType));
try try
{ {
await handler.HandleAsync(context); await handler.HandleAsync(context);
} }
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); context.Logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName);
throw; throw;
} }
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName);
} }
switch (context) switch (context)
{ {
case BaseRequestContext { IsRequestHandled: true }: case BaseRequestContext { IsRequestHandled: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseRequestContext { IsRequestSkipped: true }: case BaseRequestContext { IsRequestSkipped: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseValidatingContext { IsRejected: true }: case BaseValidatingContext { IsRejected: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
default: continue;
}
}
async IAsyncEnumerable<IOpenIddictServerHandler<TContext>> GetHandlersAsync()
{
// Note: the descriptors collection is sorted during options initialization for performance reasons.
var descriptors = _options.CurrentValue.Handlers;
if (descriptors.Count is 0)
{
yield break;
}
for (var index = 0; index < descriptors.Count; index++)
{
var descriptor = descriptors[index];
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
yield return descriptor.ServiceDescriptor switch
{
{ ImplementationInstance: IOpenIddictServerHandler<TContext> handler } => handler,
_ when _provider.GetService(descriptor.ServiceDescriptor.ServiceType)
is IOpenIddictServerHandler<TContext> handler => handler,
_ => throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType))
};
} }
} }
async ValueTask<bool> IsActiveAsync(OpenIddictServerHandlerDescriptor descriptor) async ValueTask<bool> IsActiveAsync(OpenIddictServerHandlerDescriptor descriptor)
{ {
for (var index = 0; index < descriptor.FilterTypes.Length; index++) foreach (var type in descriptor.FilterTypes)
{ {
if (_provider.GetService(descriptor.FilterTypes[index]) is not IOpenIddictServerHandlerFilter<TContext> filter) var filter = context.ServiceProvider.GetService(type) as IOpenIddictServerHandlerFilter<TContext>
{ ?? throw new InvalidOperationException(SR.FormatID0099(type));
throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index]));
}
if (!await filter.IsActiveAsync(context)) if (!await filter.IsActiveAsync(context))
{ {

10
src/OpenIddict.Server/OpenIddictServerEvents.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace OpenIddict.Server; namespace OpenIddict.Server;
@ -64,12 +65,19 @@ public static partial class OpenIddictServerEvents
/// <summary> /// <summary>
/// Gets the logger responsible for logging processed operations. /// Gets the logger responsible for logging processed operations.
/// </summary> /// </summary>
public ILogger Logger => Transaction.Logger; public ILogger Logger
=> field ??= Transaction.ServiceProvider.GetRequiredService<ILogger<OpenIddictServerDispatcher>>();
/// <summary> /// <summary>
/// Gets the OpenIddict server options. /// Gets the OpenIddict server options.
/// </summary> /// </summary>
public OpenIddictServerOptions Options => Transaction.Options; public OpenIddictServerOptions Options => Transaction.Options;
/// <summary>
/// Gets the service provider associated with the current transaction.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IServiceProvider ServiceProvider => Transaction.ServiceProvider;
} }
/// <summary> /// <summary>

3
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -29,8 +29,7 @@ public static class OpenIddictServerExtensions
builder.Services.AddLogging(); builder.Services.AddLogging();
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.TryAddScoped<IOpenIddictServerDispatcher, OpenIddictServerDispatcher>(); builder.Services.TryAddSingleton<IOpenIddictServerDispatcher, OpenIddictServerDispatcher>();
builder.Services.TryAddScoped<IOpenIddictServerFactory, OpenIddictServerFactory>();
// Register the built-in server event handlers used by the OpenIddict server components. // Register the built-in server event handlers used by the OpenIddict server components.
// Note: the order used here is not important, as the actual order is set in the options. // Note: the order used here is not important, as the actual order is set in the options.

48
src/OpenIddict.Server/OpenIddictServerFactory.cs

@ -1,48 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Server;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictServerFactory : IOpenIddictServerFactory
{
private readonly ILogger _logger;
private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerDispatcher"/> class.
/// </summary>
public OpenIddictServerFactory(
ILogger<OpenIddictServerDispatcher> logger,
IOptionsMonitor<OpenIddictServerOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc/>
public ValueTask<OpenIddictServerTransaction> CreateTransactionAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new(Task.FromCanceled<OpenIddictServerTransaction>(cancellationToken));
}
return new(new OpenIddictServerTransaction
{
CancellationToken = cancellationToken,
Logger = _logger,
Options = _options.CurrentValue
});
}
}

33
src/OpenIddict.Server/OpenIddictServerTransaction.cs

@ -6,20 +6,19 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Server; namespace OpenIddict.Server;
/// <summary> /// <summary>
/// Represents the context associated with an OpenID Connect server request. /// Represents the context associated with an OpenID Connect server operation.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerTransaction public sealed class OpenIddictServerTransaction
{ {
/// <summary> /// <summary>
/// Gets or sets the cancellation token used to determine if the operation was aborted. /// Gets the cancellation token used to determine if the operation was aborted.
/// </summary> /// </summary>
public CancellationToken CancellationToken { get; set; } public required CancellationToken CancellationToken { get; init; }
/// <summary> /// <summary>
/// Gets or sets the X.509 client certificate used by the remote peer, if available. /// Gets or sets the X.509 client certificate used by the remote peer, if available.
@ -27,7 +26,7 @@ public sealed class OpenIddictServerTransaction
public X509Certificate2? RemoteCertificate { get; set; } public X509Certificate2? RemoteCertificate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the endpoint processing the current request. /// Gets or sets the type of the endpoint processing the current transaction.
/// </summary> /// </summary>
public OpenIddictServerEndpointType EndpointType { get; set; } public OpenIddictServerEndpointType EndpointType { get; set; }
@ -42,17 +41,16 @@ public sealed class OpenIddictServerTransaction
public Uri? BaseUri { get; set; } public Uri? BaseUri { get; set; }
/// <summary> /// <summary>
/// Gets or sets the logger associated with the current request. /// Gets the options associated with the current transaction.
/// </summary> /// </summary>
public ILogger Logger { get; set; } = default!; public required OpenIddictServerOptions Options
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
/// <summary> /// <summary>
/// Gets or sets the options associated with the current request. /// Gets the additional properties associated with the current transaction.
/// </summary>
public OpenIddictServerOptions Options { get; set; } = default!;
/// <summary>
/// Gets the additional properties associated with the current request.
/// </summary> /// </summary>
public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
@ -65,4 +63,13 @@ public sealed class OpenIddictServerTransaction
/// Gets or sets the current OpenID Connect response being returned. /// Gets or sets the current OpenID Connect response being returned.
/// </summary> /// </summary>
public OpenIddictResponse? Response { get; set; } public OpenIddictResponse? Response { get; set; }
/// <summary>
/// Gets the service provider used to resolve services.
/// </summary>
public required IServiceProvider ServiceProvider
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

11
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs

@ -9,14 +9,17 @@ using System.ComponentModel;
namespace OpenIddict.Validation.AspNetCore; namespace OpenIddict.Validation.AspNetCore;
/// <summary> /// <summary>
/// Exposes the current validation transaction to the ASP.NET Core host. /// Exposes the current validation transaction to the ASP.NET Core application.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationAspNetCoreFeature public sealed class OpenIddictValidationAspNetCoreFeature
{ {
/// <summary> /// <summary>
/// Gets or sets the validation transaction that encapsulates all specific /// Gets the transaction that encapsulates all specific information about an individual operation.
/// information about an individual OpenID Connect validation request.
/// </summary> /// </summary>
public OpenIddictValidationTransaction? Transaction { get; set; } public required OpenIddictValidationTransaction Transaction
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

19
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs

@ -7,6 +7,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants; using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants;
@ -22,22 +23,17 @@ public sealed class OpenIddictValidationAspNetCoreHandler : AuthenticationHandle
IAuthenticationRequestHandler IAuthenticationRequestHandler
{ {
private readonly IOpenIddictValidationDispatcher _dispatcher; private readonly IOpenIddictValidationDispatcher _dispatcher;
private readonly IOpenIddictValidationFactory _factory;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationAspNetCoreHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictValidationAspNetCoreHandler"/> class.
/// </summary> /// </summary>
public OpenIddictValidationAspNetCoreHandler( public OpenIddictValidationAspNetCoreHandler(
IOpenIddictValidationDispatcher dispatcher, IOpenIddictValidationDispatcher dispatcher,
IOpenIddictValidationFactory factory,
IOptionsMonitor<AuthenticationSchemeOptions> options, IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder) UrlEncoder encoder)
: base(options, logger, encoder) : base(options, logger, encoder)
{ => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> HandleRequestAsync() public async Task<bool> HandleRequestAsync()
@ -57,9 +53,16 @@ public sealed class OpenIddictValidationAspNetCoreHandler : AuthenticationHandle
var transaction = Context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction; var transaction = Context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction;
if (transaction is null) if (transaction is null)
{ {
var options = Context.RequestServices.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
// Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictValidationTransaction
transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference<HttpRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(HttpRequest).FullName!] = Request },
ServiceProvider = Context.RequestServices
};
// Attach the OpenIddict validation transaction to the ASP.NET Core features // Attach the OpenIddict validation transaction to the ASP.NET Core features
// so that it can retrieved while performing challenge/forbid operations. // so that it can retrieved while performing challenge/forbid operations.

17
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs

@ -23,17 +23,8 @@ public static class OpenIddictValidationAspNetCoreHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)
{ && property is HttpRequest request ? request : null;
return null;
}
if (property is WeakReference<HttpRequest> reference && reference.TryGetTarget(out HttpRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>
@ -52,7 +43,7 @@ public static class OpenIddictValidationAspNetCoreHelpers
/// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>. /// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>.
/// </summary> /// </summary>
/// <param name="context">The context instance.</param> /// <param name="context">The context instance.</param>
/// <returns>The <see cref="OpenIddictRequest"/> instance or <c>null</c> if it couldn't be found.</returns> /// <returns>The <see cref="OpenIddictRequest"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static OpenIddictRequest? GetOpenIddictValidationRequest(this HttpContext context) public static OpenIddictRequest? GetOpenIddictValidationRequest(this HttpContext context)
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
@ -64,7 +55,7 @@ public static class OpenIddictValidationAspNetCoreHelpers
/// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>. /// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>.
/// </summary> /// </summary>
/// <param name="context">The context instance.</param> /// <param name="context">The context instance.</param>
/// <returns>The <see cref="OpenIddictResponse"/> instance or <c>null</c> if it couldn't be found.</returns> /// <returns>The <see cref="OpenIddictResponse"/> instance or <see langword="null"/> if it couldn't be found.</returns>
public static OpenIddictResponse? GetOpenIddictValidationResponse(this HttpContext context) public static OpenIddictResponse? GetOpenIddictValidationResponse(this HttpContext context)
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);

1
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs

@ -29,7 +29,6 @@ public static class OpenIddictValidationOwinExtensions
// Note: unlike regular OWIN middleware, the OpenIddict validation middleware is registered // Note: unlike regular OWIN middleware, the OpenIddict validation middleware is registered
// as a scoped service in the DI container. This allows containers that support middleware // as a scoped service in the DI container. This allows containers that support middleware
// resolution (like Autofac) to use it without requiring additional configuration. // resolution (like Autofac) to use it without requiring additional configuration.
builder.Services.TryAddScoped<OpenIddictValidationOwinHandler>();
builder.Services.TryAddScoped<OpenIddictValidationOwinMiddleware>(); builder.Services.TryAddScoped<OpenIddictValidationOwinMiddleware>();
// Register the built-in event handlers used by the OpenIddict OWIN validation components. // Register the built-in event handlers used by the OpenIddict OWIN validation components.

46
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs

@ -6,6 +6,8 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Security.Infrastructure; using Microsoft.Owin.Security.Infrastructure;
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants; using static OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants;
using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties; using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties;
@ -18,25 +20,20 @@ namespace OpenIddict.Validation.Owin;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<AuthenticationOptions> public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<AuthenticationOptions>
{ {
private readonly IOpenIddictValidationDispatcher _dispatcher; private readonly IServiceProvider _provider;
private readonly IOpenIddictValidationFactory _factory;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationOwinHandler"/> class. /// Creates a new instance of the <see cref="OpenIddictValidationOwinHandler"/> class.
/// </summary> /// </summary>
/// <param name="dispatcher">The OpenIddict validation provider used by this instance.</param> /// <param name="provider">The service provider.</param>
/// <param name="factory">The OpenIddict validation factory used by this instance.</param> public OpenIddictValidationOwinHandler(IServiceProvider provider)
public OpenIddictValidationOwinHandler( => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
IOpenIddictValidationDispatcher dispatcher,
IOpenIddictValidationFactory factory)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task InitializeCoreAsync() protected override async Task InitializeCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictValidationDispatcher>();
// Note: to ensure internal operations are not immediately cancelled when the request is aborted // Note: to ensure internal operations are not immediately cancelled when the request is aborted
// (which may represent a security risk if sensitive operations are in progress), an ad-hoc token // (which may represent a security risk if sensitive operations are in progress), an ad-hoc token
// source is always created and configured to be triggered 5 seconds after the request is aborted. // source is always created and configured to be triggered 5 seconds after the request is aborted.
@ -52,9 +49,16 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName); var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName);
if (transaction is null) if (transaction is null)
{ {
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
// Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers.
transaction = await _factory.CreateTransactionAsync(source.Token); transaction = new OpenIddictValidationTransaction
transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference<IOwinRequest>(Request); {
CancellationToken = source.Token,
Options = options.CurrentValue,
Properties = { [typeof(IOwinRequest).FullName!] = Request },
ServiceProvider = _provider
};
// Attach the OpenIddict validation transaction to the OWIN shared dictionary // Attach the OpenIddict validation transaction to the OWIN shared dictionary
// so that it can retrieved while performing sign-in/sign-out operations. // so that it can retrieved while performing sign-in/sign-out operations.
@ -62,7 +66,7 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
} }
var context = new ProcessRequestContext(transaction); var context = new ProcessRequestContext(transaction);
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
// Store the context in the transaction so that it can be retrieved from InvokeAsync(). // Store the context in the transaction so that it can be retrieved from InvokeAsync().
transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context);
@ -75,6 +79,8 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
// in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when
// active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case.
var dispatcher = _provider.GetRequiredService<IOpenIddictValidationDispatcher>();
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName) var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0166));
@ -101,7 +107,7 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled) if (notification.IsRequestHandled)
{ {
@ -122,6 +128,8 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync() protected override async Task<AuthenticationTicket?> AuthenticateCoreAsync()
{ {
var dispatcher = _provider.GetRequiredService<IOpenIddictValidationDispatcher>();
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName) var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0166));
@ -131,7 +139,7 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!); var context = transaction.GetProperty<ProcessAuthenticationContext>(typeof(ProcessAuthenticationContext).FullName!);
if (context is null) if (context is null)
{ {
await _dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction)); await dispatcher.DispatchAsync(context = new ProcessAuthenticationContext(transaction));
// Store the context object in the transaction so it can be later retrieved by handlers // Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow. // that want to access the authentication result without triggering a new authentication flow.
@ -211,6 +219,8 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
// OpenIddictValidationOwinMiddleware is assumed to be the only middleware allowed to write // OpenIddictValidationOwinMiddleware is assumed to be the only middleware allowed to write
// to the response stream when a response grant (sign-in/out or challenge) was applied. // to the response stream when a response grant (sign-in/out or challenge) was applied.
var dispatcher = _provider.GetRequiredService<IOpenIddictValidationDispatcher>();
// Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code
// corresponds to a challenge response, as LookupChallenge() will always return a non-null // corresponds to a challenge response, as LookupChallenge() will always return a non-null
// value when active authentication is used, even if no challenge was actually triggered. // value when active authentication is used, even if no challenge was actually triggered.
@ -227,7 +237,7 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(context); await dispatcher.DispatchAsync(context);
if (context.IsRequestHandled || context.IsRequestSkipped) if (context.IsRequestHandled || context.IsRequestSkipped)
{ {
@ -244,7 +254,7 @@ public sealed class OpenIddictValidationOwinHandler : AuthenticationHandler<Auth
Response = new OpenIddictResponse() Response = new OpenIddictResponse()
}; };
await _dispatcher.DispatchAsync(notification); await dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled || context.IsRequestSkipped) if (notification.IsRequestHandled || context.IsRequestSkipped)
{ {

13
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs

@ -37,17 +37,8 @@ public static class OpenIddictValidationOwinHelpers
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) return transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)
{ && property is IOwinRequest request ? request : null;
return null;
}
if (property is WeakReference<IOwinRequest> reference && reference.TryGetTarget(out IOwinRequest? request))
{
return request;
}
return null;
} }
/// <summary> /// <summary>

12
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs

@ -13,10 +13,12 @@ namespace OpenIddict.Validation.Owin;
/// <summary> /// <summary>
/// Provides the entry point necessary to register the OpenIddict validation handler in an OWIN pipeline. /// Provides the entry point necessary to register the OpenIddict validation handler in an OWIN pipeline.
/// </summary>
/// <remarks>
/// Note: this middleware is intended to be used with dependency injection containers /// Note: this middleware is intended to be used with dependency injection containers
/// that support middleware resolution, like Autofac. Since it depends on scoped services, /// that support middleware resolution, like Autofac. Since it depends on scoped services,
/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. /// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware.
/// </summary> /// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions> public sealed class OpenIddictValidationOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions>
{ {
@ -32,9 +34,8 @@ public sealed class OpenIddictValidationOwinMiddleware : AuthenticationMiddlewar
IServiceProvider provider) IServiceProvider provider)
: base(next, new InternalOptions() : base(next, new InternalOptions()
{ {
AuthenticationMode = provider.GetService<IOptionsMonitor<OpenIddictValidationOwinOptions>>() AuthenticationMode = provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOwinOptions>>()
?.CurrentValue.AuthenticationMode .CurrentValue.AuthenticationMode
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0169))
}) })
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider)); => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
@ -43,8 +44,7 @@ public sealed class OpenIddictValidationOwinMiddleware : AuthenticationMiddlewar
/// </summary> /// </summary>
/// <returns>A new instance of the <see cref="OpenIddictValidationOwinHandler"/> class.</returns> /// <returns>A new instance of the <see cref="OpenIddictValidationOwinHandler"/> class.</returns>
protected override AuthenticationHandler<AuthenticationOptions> CreateHandler() protected override AuthenticationHandler<AuthenticationOptions> CreateHandler()
=> _provider.GetService<OpenIddictValidationOwinHandler>() => new OpenIddictValidationOwinHandler(_provider);
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0169));
/// <summary> /// <summary>
/// Provides the options used by the <see cref="OpenIddictValidationOwinMiddleware"/> class. /// Provides the options used by the <see cref="OpenIddictValidationOwinMiddleware"/> class.

30
src/OpenIddict.Validation/IOpenIddictValidationFactory.cs

@ -1,30 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
namespace OpenIddict.Validation;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IOpenIddictValidationFactory
{
/// <summary>
/// Creates a new <see cref="OpenIddictValidationTransaction"/> that is used as a
/// way to store per-request data needed to process the requested operation.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>
/// Note: the specified <see cref="CancellationToken"/> is automatically attached to the returned transaction.
/// </remarks>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous
/// operation, whose result returns the created transaction.
/// </returns>
ValueTask<OpenIddictValidationTransaction> CreateTransactionAsync(CancellationToken cancellationToken);
}

89
src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs

@ -6,7 +6,6 @@
using System.ComponentModel; using System.ComponentModel;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Validation; namespace OpenIddict.Validation;
@ -16,113 +15,73 @@ namespace OpenIddict.Validation;
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationDispatcher : IOpenIddictValidationDispatcher public sealed class OpenIddictValidationDispatcher : IOpenIddictValidationDispatcher
{ {
private readonly ILogger<OpenIddictValidationDispatcher> _logger;
private readonly IOptionsMonitor<OpenIddictValidationOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationDispatcher"/> class.
/// </summary>
public OpenIddictValidationDispatcher(
ILogger<OpenIddictValidationDispatcher> logger,
IOptionsMonitor<OpenIddictValidationOptions> options,
IServiceProvider provider)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext public async ValueTask DispatchAsync<TContext>(TContext context) where TContext : BaseContext
{ {
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
await foreach (var handler in GetHandlersAsync()) // Note: the descriptors collection is sorted during options initialization for performance reasons.
foreach (var descriptor in context.Options.Handlers)
{ {
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
var handler = descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictValidationHandler<TContext>
?? context.ServiceProvider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictValidationHandler<TContext>
?? throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType));
try try
{ {
await handler.HandleAsync(context); await handler.HandleAsync(context);
} }
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); context.Logger.LogDebug(6132, exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName);
throw; throw;
} }
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6133, SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName);
} }
switch (context) switch (context)
{ {
case BaseRequestContext { IsRequestHandled: true }: case BaseRequestContext { IsRequestHandled: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6134, SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseRequestContext { IsRequestSkipped: true }: case BaseRequestContext { IsRequestSkipped: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6135, SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
case BaseValidatingContext { IsRejected: true }: case BaseValidatingContext { IsRejected: true }:
if (_logger.IsEnabled(LogLevel.Debug)) if (context.Logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); context.Logger.LogDebug(6136, SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName);
} }
return; return;
default: continue;
}
}
async IAsyncEnumerable<IOpenIddictValidationHandler<TContext>> GetHandlersAsync()
{
// Note: the descriptors collection is sorted during options initialization for performance reasons.
var descriptors = _options.CurrentValue.Handlers;
if (descriptors.Count is 0)
{
yield break;
}
for (var index = 0; index < descriptors.Count; index++)
{
var descriptor = descriptors[index];
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor))
{
continue;
}
yield return descriptor.ServiceDescriptor switch
{
{ ImplementationInstance: IOpenIddictValidationHandler<TContext> handler } => handler,
_ when _provider.GetService(descriptor.ServiceDescriptor.ServiceType)
is IOpenIddictValidationHandler<TContext> handler => handler,
_ => throw new InvalidOperationException(SR.FormatID0138(descriptor.ServiceDescriptor.ServiceType))
};
} }
} }
async ValueTask<bool> IsActiveAsync(OpenIddictValidationHandlerDescriptor descriptor) async ValueTask<bool> IsActiveAsync(OpenIddictValidationHandlerDescriptor descriptor)
{ {
for (var index = 0; index < descriptor.FilterTypes.Length; index++) foreach (var type in descriptor.FilterTypes)
{ {
if (_provider.GetService(descriptor.FilterTypes[index]) is not IOpenIddictValidationHandlerFilter<TContext> filter) var filter = context.ServiceProvider.GetService(type) as IOpenIddictValidationHandlerFilter<TContext>
{ ?? throw new InvalidOperationException(SR.FormatID0099(type));
throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index]));
}
if (!await filter.IsActiveAsync(context)) if (!await filter.IsActiveAsync(context))
{ {

10
src/OpenIddict.Validation/OpenIddictValidationEvents.cs

@ -7,6 +7,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace OpenIddict.Validation; namespace OpenIddict.Validation;
@ -74,12 +75,19 @@ public static partial class OpenIddictValidationEvents
/// <summary> /// <summary>
/// Gets the logger responsible for logging processed operations. /// Gets the logger responsible for logging processed operations.
/// </summary> /// </summary>
public ILogger Logger => Transaction.Logger; public ILogger Logger
=> field ??= Transaction.ServiceProvider.GetRequiredService<ILogger<OpenIddictValidationDispatcher>>();
/// <summary> /// <summary>
/// Gets the OpenIddict validation options. /// Gets the OpenIddict validation options.
/// </summary> /// </summary>
public OpenIddictValidationOptions Options => Transaction.Options; public OpenIddictValidationOptions Options => Transaction.Options;
/// <summary>
/// Gets the service provider associated with the current transaction.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IServiceProvider ServiceProvider => Transaction.ServiceProvider;
} }
/// <summary> /// <summary>

3
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -30,8 +30,7 @@ public static class OpenIddictValidationExtensions
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.TryAddSingleton<OpenIddictValidationService>(); builder.Services.TryAddSingleton<OpenIddictValidationService>();
builder.Services.TryAddScoped<IOpenIddictValidationDispatcher, OpenIddictValidationDispatcher>(); builder.Services.TryAddSingleton<IOpenIddictValidationDispatcher, OpenIddictValidationDispatcher>();
builder.Services.TryAddScoped<IOpenIddictValidationFactory, OpenIddictValidationFactory>();
// Register the built-in validation event handlers used by the OpenIddict validation components. // Register the built-in validation event handlers used by the OpenIddict validation components.
// Note: the order used here is not important, as the actual order is set in the options. // Note: the order used here is not important, as the actual order is set in the options.

48
src/OpenIddict.Validation/OpenIddictValidationFactory.cs

@ -1,48 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Validation;
/// <summary>
/// Represents a service responsible for creating transactions.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictValidationFactory : IOpenIddictValidationFactory
{
private readonly ILogger<OpenIddictValidationDispatcher> _logger;
private readonly IOptionsMonitor<OpenIddictValidationOptions> _options;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationFactory"/> class.
/// </summary>
public OpenIddictValidationFactory(
ILogger<OpenIddictValidationDispatcher> logger,
IOptionsMonitor<OpenIddictValidationOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc/>
public ValueTask<OpenIddictValidationTransaction> CreateTransactionAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new(Task.FromCanceled<OpenIddictValidationTransaction>(cancellationToken));
}
return new(new OpenIddictValidationTransaction
{
CancellationToken = cancellationToken,
Logger = _logger,
Options = _options.CurrentValue
});
}
}

53
src/OpenIddict.Validation/OpenIddictValidationService.cs

@ -9,6 +9,7 @@ using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
@ -40,14 +41,17 @@ public class OpenIddictValidationService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictValidationTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = _provider
};
var context = new ProcessAuthenticationContext(transaction) var context = new ProcessAuthenticationContext(transaction)
{ {
@ -85,14 +89,17 @@ public class OpenIddictValidationService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictValidationTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = _provider
};
var request = new OpenIddictRequest(); var request = new OpenIddictRequest();
request = await PrepareConfigurationRequestAsync(); request = await PrepareConfigurationRequestAsync();
@ -207,14 +214,17 @@ public class OpenIddictValidationService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictValidationTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = _provider
};
var request = new OpenIddictRequest(); var request = new OpenIddictRequest();
request = await PrepareJsonWebKeySetRequestAsync(); request = await PrepareJsonWebKeySetRequestAsync();
@ -337,14 +347,17 @@ public class OpenIddictValidationService
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
// directly depend on scoped services like the event dispatcher. To work around
// this limitation, a scope is manually created for each method to this service.
await using var scope = _provider.CreateAsyncScope(); await using var scope = _provider.CreateAsyncScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>(); var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationFactory>(); var options = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>();
var transaction = await factory.CreateTransactionAsync(cancellationToken);
var transaction = new OpenIddictValidationTransaction
{
CancellationToken = cancellationToken,
Options = options.CurrentValue,
ServiceProvider = _provider
};
request = await PrepareIntrospectionRequestAsync(); request = await PrepareIntrospectionRequestAsync();
request = await ApplyIntrospectionRequestAsync(); request = await ApplyIntrospectionRequestAsync();

41
src/OpenIddict.Validation/OpenIddictValidationTransaction.cs

@ -6,20 +6,19 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Validation; namespace OpenIddict.Validation;
/// <summary> /// <summary>
/// Represents the context associated with an OpenID Connect validation request. /// Represents the context associated with an OpenID Connect validation operation.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)] [EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationTransaction public sealed class OpenIddictValidationTransaction
{ {
/// <summary> /// <summary>
/// Gets or sets the cancellation token used to determine if the operation was aborted. /// Gets the cancellation token used to determine if the operation was aborted.
/// </summary> /// </summary>
public CancellationToken CancellationToken { get; set; } public required CancellationToken CancellationToken { get; init; }
/// <summary> /// <summary>
/// Gets or sets the X.509 client certificate used by the remote peer, if available. /// Gets or sets the X.509 client certificate used by the remote peer, if available.
@ -27,7 +26,7 @@ public sealed class OpenIddictValidationTransaction
public X509Certificate2? RemoteCertificate { get; set; } public X509Certificate2? RemoteCertificate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the endpoint processing the current request. /// Gets or sets the type of the endpoint processing the current transaction.
/// </summary> /// </summary>
public OpenIddictValidationEndpointType EndpointType { get; set; } public OpenIddictValidationEndpointType EndpointType { get; set; }
@ -42,24 +41,27 @@ public sealed class OpenIddictValidationTransaction
public Uri? BaseUri { get; set; } public Uri? BaseUri { get; set; }
/// <summary> /// <summary>
/// Gets or sets the logger associated with the current request. /// Gets the options associated with the current transaction.
/// </summary> /// </summary>
public ILogger Logger { get; set; } = default!; public required OpenIddictValidationOptions Options
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
/// <summary> /// <summary>
/// Gets or sets the options associated with the current request. /// Gets the additional properties associated with the current transaction.
/// </summary>
public OpenIddictValidationOptions Options { get; set; } = default!;
/// <summary>
/// Gets the additional properties associated with the current request.
/// </summary> /// </summary>
public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Gets or sets the server configuration used for the current request. /// Gets or sets the server configuration used for the current transaction.
/// </summary> /// </summary>
public OpenIddictConfiguration Configuration { get; set; } = default!; public OpenIddictConfiguration Configuration
{
get;
set { ArgumentNullException.ThrowIfNull(value); field = value; }
} = default!;
/// <summary> /// <summary>
/// Gets or sets the current OpenID Connect request. /// Gets or sets the current OpenID Connect request.
@ -70,4 +72,13 @@ public sealed class OpenIddictValidationTransaction
/// Gets or sets the current OpenID Connect response being returned. /// Gets or sets the current OpenID Connect response being returned.
/// </summary> /// </summary>
public OpenIddictResponse? Response { get; set; } public OpenIddictResponse? Response { get; set; }
/// <summary>
/// Gets the service provider used to resolve services.
/// </summary>
public required IServiceProvider ServiceProvider
{
get;
init { ArgumentNullException.ThrowIfNull(value); field = value; }
}
} }

18
test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs

@ -79,23 +79,7 @@ public class OpenIddictServerExtensionsTests
// Assert // Assert
Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerDispatcher) && Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerDispatcher) &&
service.ImplementationType == typeof(OpenIddictServerDispatcher) && service.ImplementationType == typeof(OpenIddictServerDispatcher) &&
service.Lifetime is ServiceLifetime.Scoped); service.Lifetime is ServiceLifetime.Singleton);
}
[Fact]
public void AddServer_RegistersServerFactory()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddServer();
// Assert
Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerFactory) &&
service.ImplementationType == typeof(OpenIddictServerFactory) &&
service.Lifetime is ServiceLifetime.Scoped);
} }
public static IEnumerable<object[]> DefaultHandlers public static IEnumerable<object[]> DefaultHandlers

Loading…
Cancel
Save