/* * 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; using OpenIddict.Extensions; namespace OpenIddict.Client; public static partial class OpenIddictClientHandlers { public static class Session { public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create([ /* * EndSession request top-level processing: */ PrepareEndSessionRequest.Descriptor, ApplyEndSessionRequest.Descriptor, /* * EndSession request processing: */ AttachEndSessionEndpoint.Descriptor, /* * Post-logout redirection request top-level processing: */ ExtractPostLogoutRedirectionRequest.Descriptor, ValidatePostLogoutRedirectionRequest.Descriptor, HandlePostLogoutRedirectionRequest.Descriptor, ApplyPostLogoutRedirectionResponse.Descriptor, ApplyPostLogoutRedirectionResponse.Descriptor, /* * Post-logout redirection request validation: */ ValidateTokens.Descriptor ]); /// /// Contains the logic responsible for preparing authorization requests and invoking the corresponding event handlers. /// public sealed class PrepareEndSessionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public PrepareEndSessionRequest(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.MaxValue - 100_000) .Build(); /// public async ValueTask HandleAsync(ProcessSignOutContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new PrepareEndSessionRequestContext(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 ApplyEndSessionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ApplyEndSessionRequest(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(PrepareEndSessionRequest.Descriptor.Order + 1_000) .Build(); /// public async ValueTask HandleAsync(ProcessSignOutContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ApplyEndSessionRequestContext(context.Transaction) { // Note: the endpoint URI is automatically set by a specialized handler if it's not set here. EndSessionEndpoint = context.EndSessionEndpoint?.AbsoluteUri!, PostLogoutRedirectUri = context.PostLogoutRedirectUri }; 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; } } } /// /// Contains the logic responsible for attaching the URI of the authorization request to the request. /// public sealed class AttachEndSessionEndpoint : 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(ApplyEndSessionRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } // Don't overwrite the endpoint URI if it was already set. if (!string.IsNullOrEmpty(context.EndSessionEndpoint)) { return default; } // Ensure the end session endpoint is present and is a valid absolute URI. if (context.Configuration.EndSessionEndpoint is not { IsAbsoluteUri: true } || OpenIddictHelpers.IsImplicitFileUri(context.Configuration.EndSessionEndpoint)) { throw new InvalidOperationException(SR.FormatID0301(Metadata.EndSessionEndpoint)); } context.EndSessionEndpoint = context.Configuration.EndSessionEndpoint.AbsoluteUri; return default; } } /// /// Contains the logic responsible for extracting redirection requests and invoking the corresponding event handlers. /// public sealed class ExtractPostLogoutRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ExtractPostLogoutRedirectionRequest(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 ExtractPostLogoutRedirectionRequestContext(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.ID0369)); } context.Logger.LogInformation(SR.GetResourceString(SR.ID6199), notification.Request); } } /// /// Contains the logic responsible for validating redirection requests and invoking the corresponding event handlers. /// public sealed class ValidatePostLogoutRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public ValidatePostLogoutRedirectionRequest(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(ExtractPostLogoutRedirectionRequest.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 ValidatePostLogoutRedirectionRequestContext(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.ID6200)); } } /// /// Contains the logic responsible for handling redirection requests and invoking the corresponding event handlers. /// public sealed class HandlePostLogoutRedirectionRequest : IOpenIddictClientHandler { private readonly IOpenIddictClientDispatcher _dispatcher; public HandlePostLogoutRedirectionRequest(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(ValidatePostLogoutRedirectionRequest.Descriptor.Order + 1_000) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new HandlePostLogoutRedirectionRequestContext(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.Transaction.Response = new OpenIddictResponse(); } } /// /// Contains the logic responsible for processing redirection responses and invoking the corresponding event handlers. /// public sealed class ApplyPostLogoutRedirectionResponse : IOpenIddictClientHandler where TContext : BaseRequestContext { private readonly IOpenIddictClientDispatcher _dispatcher; public ApplyPostLogoutRedirectionResponse(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(500_000) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new ApplyPostLogoutRedirectionResponseContext(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.ID0371)); } } /// /// 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(ValidatePostLogoutRedirectionRequestContext 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; } } } }