Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

473 lines
19 KiB

/*
* 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<OpenIddictClientHandlerDescriptor> 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<ProcessErrorContext>.Descriptor,
ApplyPostLogoutRedirectionResponse<ProcessRequestContext>.Descriptor,
/*
* Post-logout redirection request validation:
*/
ValidateTokens.Descriptor
]);
/// <summary>
/// Contains the logic responsible for preparing authorization requests and invoking the corresponding event handlers.
/// </summary>
public sealed class PrepareEndSessionRequest : IOpenIddictClientHandler<ProcessSignOutContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public PrepareEndSessionRequest(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.UseScopedHandler<PrepareEndSessionRequest>()
.SetOrder(int.MaxValue - 100_000)
.Build();
/// <inheritdoc/>
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;
}
}
}
/// <summary>
/// Contains the logic responsible for applying authorization requests and invoking the corresponding event handlers.
/// </summary>
public sealed class ApplyEndSessionRequest : IOpenIddictClientHandler<ProcessSignOutContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public ApplyEndSessionRequest(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.UseScopedHandler<ApplyEndSessionRequest>()
.SetOrder(PrepareEndSessionRequest.Descriptor.Order + 1_000)
.Build();
/// <inheritdoc/>
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;
}
}
}
/// <summary>
/// Contains the logic responsible for attaching the URI of the authorization request to the request.
/// </summary>
public sealed class AttachEndSessionEndpoint : IOpenIddictClientHandler<ApplyEndSessionRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyEndSessionRequestContext>()
.UseSingletonHandler<AttachEndSessionEndpoint>()
.SetOrder(int.MinValue + 100_000)
.Build();
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Contains the logic responsible for extracting redirection requests and invoking the corresponding event handlers.
/// </summary>
public sealed class ExtractPostLogoutRedirectionRequest : IOpenIddictClientHandler<ProcessRequestContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public ExtractPostLogoutRedirectionRequest(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequirePostLogoutRedirectionRequest>()
.UseScopedHandler<ExtractPostLogoutRedirectionRequest>()
.SetOrder(100_000)
.Build();
/// <inheritdoc/>
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);
}
}
/// <summary>
/// Contains the logic responsible for validating redirection requests and invoking the corresponding event handlers.
/// </summary>
public sealed class ValidatePostLogoutRedirectionRequest : IOpenIddictClientHandler<ProcessRequestContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public ValidatePostLogoutRedirectionRequest(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequirePostLogoutRedirectionRequest>()
.UseScopedHandler<ValidatePostLogoutRedirectionRequest>()
.SetOrder(ExtractPostLogoutRedirectionRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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));
}
}
/// <summary>
/// Contains the logic responsible for handling redirection requests and invoking the corresponding event handlers.
/// </summary>
public sealed class HandlePostLogoutRedirectionRequest : IOpenIddictClientHandler<ProcessRequestContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public HandlePostLogoutRedirectionRequest(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequirePostLogoutRedirectionRequest>()
.UseScopedHandler<HandlePostLogoutRedirectionRequest>()
.SetOrder(ValidatePostLogoutRedirectionRequest.Descriptor.Order + 1_000)
.Build();
/// <inheritdoc/>
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();
}
}
/// <summary>
/// Contains the logic responsible for processing redirection responses and invoking the corresponding event handlers.
/// </summary>
public sealed class ApplyPostLogoutRedirectionResponse<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseRequestContext
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public ApplyPostLogoutRedirectionResponse(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequirePostLogoutRedirectionRequest>()
.UseScopedHandler<ApplyPostLogoutRedirectionResponse<TContext>>()
.SetOrder(500_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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));
}
}
/// <summary>
/// Contains the logic responsible for rejecting redirection requests that don't
/// specify a valid access token, authorization code, identity token or state token.
/// </summary>
public sealed class ValidateTokens : IOpenIddictClientHandler<ValidatePostLogoutRedirectionRequestContext>
{
private readonly IOpenIddictClientDispatcher _dispatcher;
public ValidateTokens(IOpenIddictClientDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidatePostLogoutRedirectionRequestContext>()
.UseScopedHandler<ValidateTokens>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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;
}
}
}
}