/* * 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.Collections.Immutable; using Microsoft.Extensions.Logging; namespace OpenIddict.Client; public static partial class OpenIddictClientHandlers { public static class Authentication { public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( /* * Authorization request top-level processing: */ PrepareAuthorizationRequest.Descriptor, ApplyAuthorizationRequest.Descriptor, /* * Authorization request preparation: */ NormalizeResponseModeParameter.Descriptor, /* * Authorization request processing: */ AttachAuthorizationEndpoint.Descriptor, /* * Redirection request top-level processing: */ ExtractRedirectionRequest.Descriptor, ValidateRedirectionRequest.Descriptor, HandleRedirectionRequest.Descriptor, ApplyRedirectionResponse.Descriptor, ApplyRedirectionResponse.Descriptor, /* * Redirection request validation: */ ValidateTokens.Descriptor); /// /// Contains the logic responsible for preparing authorization requests and invoking the corresponding event handlers. /// public sealed class PrepareAuthorizationRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public PrepareAuthorizationRequest(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(int.MaxValue - 100_000) .Build(); /// public async ValueTask HandleAsync(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new PrepareAuthorizationRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } } } /// /// Contains the logic responsible for applying authorization requests and invoking the corresponding event handlers. /// public sealed class ApplyAuthorizationRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ApplyAuthorizationRequest(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(PrepareAuthorizationRequest.Descriptor.Order + 1_000) .Build(); /// public async ValueTask HandleAsync(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ApplyAuthorizationRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } } } /// /// Contains the logic responsible for attaching the URI of the authorization request to the request. /// public sealed class AttachAuthorizationEndpoint : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(int.MinValue + 100_000) .Build(); /// public ValueTask HandleAsync(ApplyAuthorizationRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } // Ensure the authorization endpoint is present and is a valid absolute URI. if (context.Configuration.AuthorizationEndpoint is not { IsAbsoluteUri: true } || !context.Configuration.AuthorizationEndpoint.IsWellFormedOriginalString()) { throw new InvalidOperationException(SR.FormatID0301(Metadata.AuthorizationEndpoint)); } context.AuthorizationEndpoint = context.Configuration.AuthorizationEndpoint.AbsoluteUri; return default; } } /// /// Contains the logic responsible for extracting redirection requests and invoking the corresponding event handlers. /// public sealed class ExtractRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ExtractRedirectionRequest(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(100_000) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ExtractRedirectionRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } else if (notification.IsRejected) { context.Reject( error: notification.Error ?? Errors.InvalidRequest, description: notification.ErrorDescription, uri: notification.ErrorUri); return; } if (notification.Request is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0302)); } context.Logger.LogInformation(SR.GetResourceString(SR.ID6178), notification.Request); } } /// /// Contains the logic responsible for validating redirection requests and invoking the corresponding event handlers. /// public sealed class ValidateRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ValidateRedirectionRequest(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ExtractRedirectionRequest.Descriptor.Order + 1_000) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ValidateRedirectionRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } else if (notification.IsRejected) { context.Reject( error: notification.Error ?? Errors.InvalidRequest, description: notification.ErrorDescription, uri: notification.ErrorUri); return; } context.Logger.LogInformation(SR.GetResourceString(SR.ID6179)); } } /// /// Contains the logic responsible for handling redirection requests and invoking the corresponding event handlers. /// public sealed class HandleRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public HandleRedirectionRequest(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateRedirectionRequest.Descriptor.Order + 1_000) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new HandleRedirectionRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } else if (notification.IsRejected) { context.Reject( error: notification.Error ?? Errors.InvalidRequest, description: notification.ErrorDescription, uri: notification.ErrorUri); return; } throw new InvalidOperationException(SR.GetResourceString(SR.ID0368)); } } /// /// Contains the logic responsible for processing redirection responses and invoking the corresponding event handlers. /// public sealed class ApplyRedirectionResponse : IOpenIddictClientHandler where TContext : BaseRequestContext { private readonly IOpenIddictClientDispatcher _dispatcher; public ApplyRedirectionResponse(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler>() .SetOrder(int.MaxValue - 100_000) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ApplyRedirectionResponseContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } throw new InvalidOperationException(SR.GetResourceString(SR.ID0303)); } } /// /// Contains the logic responsible for removing the response mode parameter from the /// request if it corresponds to the default mode for the selected response type. /// public sealed class NormalizeResponseModeParameter : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(int.MinValue + 100_000) .Build(); /// public ValueTask HandleAsync(PrepareAuthorizationRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } // When the response mode corresponds to the default mode assigned to the selected // response type, the specification explicitly recommends omitting the response mode. // As such, this handler is expected to remove the mode parameter in the following cases: // - Authorization code flow: response_mode=query. // - Hybrid flow: response_mode=fragment. // - Implicit flow: response_mode=fragment. // // For more information, read // https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes. // if (!string.IsNullOrEmpty(context.Request.ResponseMode) && (context.Request.IsAuthorizationCodeFlow() && context.Request.IsQueryResponseMode()) || (context.Request.IsHybridFlow() && context.Request.IsFragmentResponseMode()) || (context.Request.IsImplicitFlow() && context.Request.IsFragmentResponseMode())) { context.Request.ResponseMode = null; } return default; } } /// /// Contains the logic responsible for rejecting redirection requests that don't /// specify a valid access token, authorization code, identity token or state token. /// public sealed class ValidateTokens : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ValidateTokens(IOpenIddictClientDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .UseScopedHandler() .SetOrder(int.MinValue + 100_000) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ValidateRedirectionRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ProcessAuthenticationContext(context.Transaction); await _dispatcher.DispatchAsync(notification); // 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. context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } else if (notification.IsRejected) { context.Reject( error: notification.Error ?? Errors.InvalidRequest, description: notification.ErrorDescription, uri: notification.ErrorUri); return; } // Attach the security principals extracted from the tokens to the validation context. context.Principal = notification.FrontchannelIdentityTokenPrincipal; context.StateTokenPrincipal = notification.StateTokenPrincipal; } } } }