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.
 
 
 
 
 
 

1038 lines
49 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;
using System.Collections.Immutable;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using SR = OpenIddict.Abstractions.Resources.OpenIddictResources;
namespace OpenIddict.Server
{
public static partial class OpenIddictServerHandlers
{
public static class Revocation
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Revocation request top-level processing:
*/
ExtractRevocationRequest.Descriptor,
ValidateRevocationRequest.Descriptor,
HandleRevocationRequest.Descriptor,
ApplyRevocationResponse<ProcessErrorContext>.Descriptor,
ApplyRevocationResponse<ProcessRequestContext>.Descriptor,
/*
* Revocation request validation:
*/
ValidateTokenParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
ValidateTokenType.Descriptor,
ValidateAuthorizedParty.Descriptor,
/*
* Revocation request handling:
*/
AttachPrincipal.Descriptor,
RevokeToken.Descriptor,
/*
* Revocation response handling:
*/
NormalizeErrorResponse.Descriptor);
/// <summary>
/// Contains the logic responsible of extracting revocation requests and invoking the corresponding event handlers.
/// </summary>
public class ExtractRevocationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ExtractRevocationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireRevocationRequest>()
.UseScopedHandler<ExtractRevocationRequest>()
.SetOrder(100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ExtractRevocationRequestContext(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 == null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1047));
}
context.Logger.LogInformation(SR.GetResourceString(SR.ID7109), notification.Request);
}
}
/// <summary>
/// Contains the logic responsible of validating revocation requests and invoking the corresponding event handlers.
/// </summary>
public class ValidateRevocationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateRevocationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireRevocationRequest>()
.UseScopedHandler<ValidateRevocationRequest>()
.SetOrder(ExtractRevocationRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ValidateRevocationRequestContext(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 principal without triggering a new validation process.
context.Transaction.SetProperty(typeof(ValidateRevocationRequestContext).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;
}
context.Logger.LogInformation(SR.GetResourceString(SR.ID7110));
}
}
/// <summary>
/// Contains the logic responsible of handling revocation requests and invoking the corresponding event handlers.
/// </summary>
public class HandleRevocationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public HandleRevocationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireRevocationRequest>()
.UseScopedHandler<HandleRevocationRequest>()
.SetOrder(ValidateRevocationRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new HandleRevocationRequestContext(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.Response = new OpenIddictResponse();
}
}
/// <summary>
/// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers.
/// </summary>
public class ApplyRevocationResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ApplyRevocationResponse([NotNull] IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireRevocationRequest>()
.UseScopedHandler<ApplyRevocationResponse<TContext>>()
.SetOrder(int.MaxValue - 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ApplyRevocationResponseContext(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.ID1048));
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that don't specify a token.
/// </summary>
public class ValidateTokenParameter : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseSingletonHandler<ValidateTokenParameter>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject revocation requests missing the mandatory token parameter.
if (string.IsNullOrEmpty(context.Request.Token))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7111), Parameters.Token);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID3029, Parameters.Token]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that don't specify a client identifier.
/// </summary>
public class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseSingletonHandler<ValidateClientIdParameter>()
.SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// At this stage, reject the revocation request unless the client identification requirement was disabled.
if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7111), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: context.Localizer[SR.ID3029, Parameters.ClientId]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientId : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
public ValidateClientId([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
context.Logger.LogError(SR.GetResourceString(SR.ID7112), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: context.Localizer[SR.ID3052, Parameters.ClientId]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests made by applications
/// whose client type is not compatible with the presence or absence of a client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientType : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
public ValidateClientType([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1031));
}
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Reject revocation requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7113), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: context.Localizer[SR.ID3061, Parameters.ClientSecret]);
return;
}
return;
}
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7114), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: context.Localizer[SR.ID3062, Parameters.ClientSecret]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientSecret : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
public ValidateClientSecret([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1031));
}
// If the application is not a public client, validate the client secret.
if (!await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public) &&
!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7115), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: context.Localizer[SR.ID3055]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests made by
/// applications that haven't been granted the revocation endpoint permission.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateEndpointPermissions : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
public ValidateEndpointPermissions([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1031));
}
// Reject the request if the application is not allowed to use the revocation endpoint.
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Revocation))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7116), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID3078]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that don't specify a valid token.
/// </summary>
public class ValidateToken : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken([NotNull] IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ProcessAuthenticationContext(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;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that specify an unsupported token.
/// </summary>
public class ValidateTokenType : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseSingletonHandler<ValidateTokenType>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
!context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode) &&
!context.Principal.HasTokenType(TokenTypeHints.RefreshToken))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7117));
context.Reject(
error: Errors.UnsupportedTokenType,
description: context.Localizer[SR.ID3079]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests that specify a token
/// that cannot be revoked by the client application sending the revocation requests.
/// </summary>
public class ValidateAuthorizedParty : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
// Note: when client identification is not enforced, this handler cannot validate
// the audiences/presenters if the client_id of the calling application is not known.
// In this case, the risk is quite limited as claims are never returned by this endpoint.
.AddFilter<RequireClientIdParameter>()
.UseSingletonHandler<ValidateAuthorizedParty>()
.SetOrder(ValidateTokenType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// When the revoked token is an authorization code, the caller must be
// listed as a presenter (i.e the party the authorization code was issued to).
if (context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode))
{
if (!context.Principal.HasPresenter())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1042));
}
if (!context.Principal.HasPresenter(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7118));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
return default;
}
// When the revoked token is an access token, the caller must be listed either as a presenter
// (i.e the party the token was issued to) or as an audience (i.e a resource server/API).
// If the access token doesn't contain any explicit presenter/audience, the token is assumed
// to be not specific to any resource server/client application and the check is bypassed.
if (context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
context.Principal.HasAudience() && !context.Principal.HasAudience(context.ClientId) &&
context.Principal.HasPresenter() && !context.Principal.HasPresenter(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7119));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
// When the revoked token is an identity token, the caller must be listed as an audience
// (i.e the client application the identity token was initially issued to).
// If the identity token doesn't contain any explicit audience, the token is
// assumed to be not specific to any client application and the check is bypassed.
if (context.Principal.HasTokenType(TokenTypeHints.IdToken) &&
context.Principal.HasAudience() && !context.Principal.HasAudience(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7120));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
// When the revoked token is a refresh token, the caller must be
// listed as a presenter (i.e the party the token was issued to).
// If the refresh token doesn't contain any explicit presenter, the token is
// assumed to be not specific to any client application and the check is bypassed.
if (context.Principal.HasTokenType(TokenTypeHints.RefreshToken) &&
context.Principal.HasPresenter() && !context.Principal.HasPresenter(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7121));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of attaching the principal
/// extracted from the revoked token to the event context.
/// </summary>
public class AttachPrincipal : IOpenIddictServerHandler<HandleRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleRevocationRequestContext>()
.UseSingletonHandler<AttachPrincipal>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] HandleRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = context.Transaction.GetProperty<ValidateRevocationRequestContext>(
typeof(ValidateRevocationRequestContext).FullName) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID1006));
context.Principal ??= notification.Principal;
return default;
}
}
/// <summary>
/// Contains the logic responsible of revoking the token sent by the client application.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class RevokeToken : IOpenIddictServerHandler<HandleRevocationRequestContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public RevokeToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
public RevokeToken([NotNull] IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleRevocationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<RevokeToken>()
.SetOrder(AttachPrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] HandleRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Extract the token identifier from the authentication principal.
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7122));
context.Reject(
error: Errors.UnsupportedTokenType,
description: context.Localizer[SR.ID3079]);
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token == null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID7123), identifier);
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3004]);
return;
}
// Try to revoke the token. If an error occurs, return an error.
if (!await _tokenManager.TryRevokeAsync(token))
{
context.Reject(
error: Errors.UnsupportedTokenType,
description: context.Localizer[SR.ID3079]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of converting revocation errors to standard empty responses.
/// </summary>
public class NormalizeErrorResponse : IOpenIddictServerHandler<ApplyRevocationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyRevocationResponseContext>()
.UseSingletonHandler<NormalizeErrorResponse>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] ApplyRevocationResponseContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(context.Error))
{
return default;
}
// If the error indicates an invalid token, remove the error details, as required by the revocation
// specification. Visit https://tools.ietf.org/html/rfc7009#section-2.2 for more information.
// While this prevent the resource server from determining the root cause of the revocation failure,
// this is required to keep OpenIddict fully standard and compatible with all revocation clients.
if (string.Equals(context.Error, Errors.InvalidToken, StringComparison.Ordinal))
{
context.Response.Error = null;
context.Response.ErrorDescription = null;
context.Response.ErrorUri = null;
}
return default;
}
}
}
}
}