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.
1646 lines
75 KiB
1646 lines
75 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 System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OpenIddict.Extensions;
|
|
|
|
namespace OpenIddict.Server;
|
|
|
|
public static partial class OpenIddictServerHandlers
|
|
{
|
|
public static class Exchange
|
|
{
|
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } =
|
|
[
|
|
/*
|
|
* Token request top-level processing:
|
|
*/
|
|
ExtractTokenRequest.Descriptor,
|
|
ValidateTokenRequest.Descriptor,
|
|
HandleTokenRequest.Descriptor,
|
|
ApplyTokenResponse<ProcessChallengeContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessErrorContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessRequestContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessSignInContext>.Descriptor,
|
|
|
|
/*
|
|
* Token request validation:
|
|
*/
|
|
ValidateGrantType.Descriptor,
|
|
ValidateClientIdParameter.Descriptor,
|
|
ValidateAuthorizationCodeParameter.Descriptor,
|
|
ValidateClientCredentialsParameters.Descriptor,
|
|
ValidateDeviceCodeParameter.Descriptor,
|
|
ValidateRefreshTokenParameter.Descriptor,
|
|
ValidateResourceOwnerCredentialsParameters.Descriptor,
|
|
ValidateProofKeyForCodeExchangeParameters.Descriptor,
|
|
ValidateScopeParameter.Descriptor,
|
|
ValidateScopes.Descriptor,
|
|
ValidateAuthentication.Descriptor,
|
|
ValidateEndpointPermissions.Descriptor,
|
|
ValidateGrantTypePermissions.Descriptor,
|
|
ValidateScopePermissions.Descriptor,
|
|
ValidateProofKeyForCodeExchangeRequirement.Descriptor,
|
|
ValidatePresenters.Descriptor,
|
|
ValidateRedirectUri.Descriptor,
|
|
ValidateCodeVerifier.Descriptor,
|
|
ValidateGrantedScopes.Descriptor,
|
|
|
|
/*
|
|
* Token request handling:
|
|
*/
|
|
AttachPrincipal.Descriptor,
|
|
|
|
/*
|
|
* Token response handling:
|
|
*/
|
|
NormalizeErrorResponse.Descriptor
|
|
];
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for extracting token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ExtractTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ExtractTokenRequest(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.AddFilter<RequireTokenRequest>()
|
|
.UseScopedHandler<ExtractTokenRequest>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new ExtractTokenRequestContext(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.ID0040));
|
|
}
|
|
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6075), notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for validating token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ValidateTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateTokenRequest(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.AddFilter<RequireTokenRequest>()
|
|
.UseScopedHandler<ValidateTokenRequest>()
|
|
.SetOrder(ExtractTokenRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new ValidateTokenRequestContext(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(ValidateTokenRequestContext).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.ID6076));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for handling token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class HandleTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public HandleTokenRequest(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.AddFilter<RequireTokenRequest>()
|
|
.UseScopedHandler<HandleTokenRequest>()
|
|
.SetOrder(ValidateTokenRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new HandleTokenRequestContext(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.InvalidGrant,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
if (notification.Principal is not null)
|
|
{
|
|
var @event = new ProcessSignInContext(context.Transaction)
|
|
{
|
|
Principal = notification.Principal,
|
|
Response = new OpenIddictResponse()
|
|
};
|
|
|
|
if (notification.Parameters.Count > 0)
|
|
{
|
|
foreach (var parameter in notification.Parameters)
|
|
{
|
|
@event.Parameters.Add(parameter.Key, parameter.Value);
|
|
}
|
|
}
|
|
|
|
await _dispatcher.DispatchAsync(@event);
|
|
|
|
if (@event.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (@event.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (@event.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: @event.Error ?? Errors.InvalidRequest,
|
|
description: @event.ErrorDescription,
|
|
uri: @event.ErrorUri);
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0041));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for processing sign-in responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ApplyTokenResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ApplyTokenResponse(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
|
|
.AddFilter<RequireTokenRequest>()
|
|
.UseScopedHandler<ApplyTokenResponse<TContext>>()
|
|
.SetOrder(500_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(TContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new ApplyTokenResponseContext(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.ID0042));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that specify an invalid grant type.
|
|
/// </summary>
|
|
public sealed class ValidateGrantType : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateGrantType>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject token requests missing the mandatory grant_type parameter.
|
|
if (string.IsNullOrEmpty(context.Request.GrantType))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.GrantType);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.GrantType),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject token requests that don't specify a supported grant type.
|
|
if (!context.Options.GrantTypes.Contains(context.Request.GrantType))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6078), context.Request.GrantType);
|
|
|
|
context.Reject(
|
|
error: Errors.UnsupportedGrantType,
|
|
description: SR.FormatID2032(Parameters.GrantType),
|
|
uri: SR.FormatID8000(SR.ID2032));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject token requests that specify scope=offline_access if the refresh token flow is not enabled.
|
|
if (context.Request.HasScope(Scopes.OfflineAccess) &&
|
|
!context.Options.GrantTypes.Contains(GrantTypes.RefreshToken))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2035(Scopes.OfflineAccess),
|
|
uri: SR.FormatID8000(SR.ID2035));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that don't specify a client identifier.
|
|
/// </summary>
|
|
public sealed class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateClientIdParameter>()
|
|
.SetOrder(ValidateGrantType.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject grant_type=authorization_code requests that don't specify a client_id or a client_assertion,
|
|
// as the client identifier MUST be sent by the client application in the request body if it cannot
|
|
// be inferred from the client authentication method (e.g the username when using basic).
|
|
//
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
|
|
if (context.Request.IsAuthorizationCodeGrantType() &&
|
|
string.IsNullOrEmpty(context.Request.ClientId) &&
|
|
string.IsNullOrEmpty(context.Request.ClientAssertion))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.ClientId),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject grant_type=client_credentials requests that don't specify a client_id or a client_assertion.
|
|
//
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
|
|
if (context.Request.IsClientCredentialsGrantType() &&
|
|
string.IsNullOrEmpty(context.Request.ClientId) &&
|
|
string.IsNullOrEmpty(context.Request.ClientAssertion))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.ClientId),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that don't
|
|
/// specify an authorization code for the authorization code grant type.
|
|
/// </summary>
|
|
public sealed class ValidateAuthorizationCodeParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateAuthorizationCodeParameter>()
|
|
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject grant_type=authorization_code requests missing the authorization code.
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
|
|
if (context.Request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.Request.Code))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.Code);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.Code),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that specify invalid client credentials parameters.
|
|
/// </summary>
|
|
public sealed class ValidateClientCredentialsParameters : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateClientCredentialsParameters>()
|
|
.SetOrder(ValidateAuthorizationCodeParameter.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Ensure a client_assertion_type is specified when a client_assertion was attached.
|
|
if (!string.IsNullOrEmpty(context.Request.ClientAssertion) &&
|
|
string.IsNullOrEmpty(context.Request.ClientAssertionType))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2037(Parameters.ClientAssertionType, Parameters.ClientAssertion),
|
|
uri: SR.FormatID8000(SR.ID2037));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Ensure a client_assertion is specified when a client_assertion_type was attached.
|
|
if (string.IsNullOrEmpty(context.Request.ClientAssertion) &&
|
|
!string.IsNullOrEmpty(context.Request.ClientAssertionType))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2037(Parameters.ClientAssertion, Parameters.ClientAssertionType),
|
|
uri: SR.FormatID8000(SR.ID2037));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject requests that use multiple client authentication methods.
|
|
//
|
|
// See https://tools.ietf.org/html/rfc6749#section-2.3 for more information.
|
|
if (!string.IsNullOrEmpty(context.Request.ClientAssertion) &&
|
|
!string.IsNullOrEmpty(context.Request.ClientSecret))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6140));
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.GetResourceString(SR.ID2087),
|
|
uri: SR.FormatID8000(SR.ID2087));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Ensure the specified client_assertion_type is supported.
|
|
if (!string.IsNullOrEmpty(context.Request.ClientAssertionType) &&
|
|
!context.Options.ClientAssertionTypes.Contains(context.Request.ClientAssertionType))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidClient,
|
|
description: SR.FormatID2032(Parameters.ClientAssertionType),
|
|
uri: SR.FormatID8000(SR.ID2032));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject grant_type=client_credentials requests missing the client credentials.
|
|
//
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
|
|
if (context.Request.IsClientCredentialsGrantType() &&
|
|
string.IsNullOrEmpty(context.Request.ClientAssertion) &&
|
|
string.IsNullOrEmpty(context.Request.ClientSecret))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2057(Parameters.ClientSecret, Parameters.ClientAssertion),
|
|
uri: SR.FormatID8000(SR.ID2057));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that
|
|
/// don't specify a device code for the device code grant type.
|
|
/// </summary>
|
|
public sealed class ValidateDeviceCodeParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateDeviceCodeParameter>()
|
|
.SetOrder(ValidateClientCredentialsParameters.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject grant_type=urn:ietf:params:oauth:grant-type:device_code requests missing the device code.
|
|
// See https://tools.ietf.org/html/rfc8628#section-3.4 for more information.
|
|
if (context.Request.IsDeviceCodeGrantType() && string.IsNullOrEmpty(context.Request.DeviceCode))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2058(Parameters.DeviceCode),
|
|
uri: SR.FormatID8000(SR.ID2058));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that
|
|
/// specify invalid parameters for the refresh token grant type.
|
|
/// </summary>
|
|
public sealed class ValidateRefreshTokenParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateRefreshTokenParameter>()
|
|
.SetOrder(ValidateDeviceCodeParameter.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject grant_type=refresh_token requests missing the refresh token.
|
|
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
|
|
if (context.Request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(context.Request.RefreshToken))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RefreshToken);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.RefreshToken),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests
|
|
/// that specify invalid parameters for the password grant type.
|
|
/// </summary>
|
|
public sealed class ValidateResourceOwnerCredentialsParameters : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateResourceOwnerCredentialsParameters>()
|
|
.SetOrder(ValidateRefreshTokenParameter.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject grant_type=password requests missing username or password.
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.3.2 for more information.
|
|
if (context.Request.IsPasswordGrantType() && (string.IsNullOrEmpty(context.Request.Username) ||
|
|
string.IsNullOrEmpty(context.Request.Password)))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6079));
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2059(Parameters.Username, Parameters.Password),
|
|
uri: SR.FormatID8000(SR.ID2059));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that don't specify valid PKCE parameters.
|
|
/// </summary>
|
|
public sealed class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateProofKeyForCodeExchangeParameters>()
|
|
.SetOrder(ValidateResourceOwnerCredentialsParameters.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// Optimization: the ValidateCodeVerifier event handler automatically rejects grant_type=authorization_code
|
|
// requests missing the code_verifier parameter when a challenge was specified in the authorization request.
|
|
// That check requires decrypting the authorization code and determining whether a code challenge was set.
|
|
// If OpenIddict was configured to require PKCE, this can be potentially avoided by making an early check here.
|
|
if (context.Options.RequireProofKeyForCodeExchange && string.IsNullOrEmpty(context.Request.CodeVerifier))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeVerifier);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.CodeVerifier),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that specify an invalid scope parameter.
|
|
/// </summary>
|
|
public sealed class ValidateScopeParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateScopeParameter>()
|
|
.SetOrder(ValidateResourceOwnerCredentialsParameters.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject authorization code and device authorization code requests that contain a "scope" parameter.
|
|
//
|
|
// Note: using the "scope" parameter with grant_type=refresh_token is deliberately allowed
|
|
// by the specification and is typically used to retrieve an access token granting a more
|
|
// limited access than the scopes initially specified in the authorization request.
|
|
//
|
|
// For more information, see https://tools.ietf.org/html/rfc6749#section-6.
|
|
if (!string.IsNullOrEmpty(context.Request.Scope) && (context.Request.IsAuthorizationCodeGrantType() ||
|
|
context.Request.IsDeviceCodeGrantType()))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6094), Parameters.Scope);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2074(Parameters.Scope),
|
|
uri: SR.FormatID8000(SR.ID2074));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting authorization requests that use unregistered scopes.
|
|
/// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
|
|
/// </summary>
|
|
public sealed class ValidateScopes : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictScopeManager? _scopeManager;
|
|
|
|
public ValidateScopes(IOpenIddictScopeManager? scopeManager = null)
|
|
=> _scopeManager = scopeManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireScopeValidationEnabled>()
|
|
.UseScopedHandler<ValidateScopes>(static provider =>
|
|
{
|
|
// Note: the scope manager is only resolved if the degraded mode was not enabled to ensure
|
|
// invalid core configuration exceptions are not thrown even if the managers were registered.
|
|
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
|
|
|
|
return options.EnableDegradedMode ?
|
|
new ValidateScopes() :
|
|
new ValidateScopes(provider.GetService<IOpenIddictScopeManager>() ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
|
|
})
|
|
.SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// If all the specified scopes are registered in the options, avoid making a database lookup.
|
|
var scopes = context.Request.GetScopes().ToHashSet(StringComparer.Ordinal);
|
|
scopes.ExceptWith(context.Options.Scopes);
|
|
|
|
// Note: the remaining scopes are only checked if the degraded mode was not enabled,
|
|
// as this requires using the scope manager, which is never used with the degraded mode,
|
|
// even if the service was registered and resolved from the dependency injection container.
|
|
if (scopes.Count is not 0 && !context.Options.EnableDegradedMode)
|
|
{
|
|
if (_scopeManager is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
}
|
|
|
|
await foreach (var scope in _scopeManager.FindByNamesAsync([.. scopes]))
|
|
{
|
|
var name = await _scopeManager.GetNameAsync(scope);
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
scopes.Remove(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If at least one scope was not recognized, return an error.
|
|
if (scopes.Count is not 0)
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6080), scopes);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidScope,
|
|
description: SR.FormatID2052(Parameters.Scope),
|
|
uri: SR.FormatID8000(SR.ID2052));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for applying the authentication logic to token requests.
|
|
/// </summary>
|
|
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseScopedHandler<ValidateAuthentication>()
|
|
.SetOrder(ValidateScopes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext 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 principal extracted from the token to the validation context.
|
|
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
|
|
context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
|
|
context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests made by
|
|
/// applications that haven't been granted the token endpoint permission.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public sealed class ValidateEndpointPermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireEndpointPermissionsEnabled>()
|
|
.UseScopedHandler<ValidateEndpointPermissions>()
|
|
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
|
|
|
|
// Reject the request if the application is not allowed to use the token endpoint.
|
|
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Token))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6086), context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.UnauthorizedClient,
|
|
description: SR.GetResourceString(SR.ID2063),
|
|
uri: SR.FormatID8000(SR.ID2063));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests made by applications
|
|
/// that haven't been granted the appropriate grant type permissions.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public sealed class ValidateGrantTypePermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireGrantTypePermissionsEnabled>()
|
|
.UseScopedHandler<ValidateGrantTypePermissions>()
|
|
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
|
|
|
|
// Reject the request if the application is not allowed to use the specified grant type.
|
|
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.GrantType + context.Request.GrantType))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6087), context.ClientId, context.Request.GrantType);
|
|
|
|
context.Reject(
|
|
error: Errors.UnauthorizedClient,
|
|
description: SR.GetResourceString(SR.ID2064),
|
|
uri: SR.FormatID8000(SR.ID2064));
|
|
|
|
return;
|
|
}
|
|
|
|
// Reject the request if the offline_access scope was request and if
|
|
// the application is not allowed to use the refresh token grant type.
|
|
if (context.Request.HasScope(Scopes.OfflineAccess) &&
|
|
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6088), context.ClientId, Scopes.OfflineAccess);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2065(Scopes.OfflineAccess),
|
|
uri: SR.FormatID8000(SR.ID2065));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests made by applications
|
|
/// that haven't been granted the appropriate grant type permission.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public sealed class ValidateScopePermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireScopePermissionsEnabled>()
|
|
.UseScopedHandler<ValidateScopePermissions>()
|
|
.SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
|
|
|
|
foreach (var scope in context.Request.GetScopes())
|
|
{
|
|
// Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
|
|
if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
|
|
string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Reject the request if the application is not allowed to use the iterated scope.
|
|
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6089), context.ClientId, scope);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.GetResourceString(SR.ID2051),
|
|
uri: SR.FormatID8000(SR.ID2051));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests made by
|
|
/// applications for which proof key for code exchange (PKCE) was enforced.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public sealed class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.UseScopedHandler<ValidateProofKeyForCodeExchangeRequirement>()
|
|
.SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If a code_verifier was provided, the request is always considered valid,
|
|
// whether the proof key for code exchange requirement is enforced or not.
|
|
if (!string.IsNullOrEmpty(context.Request.CodeVerifier))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
|
|
|
|
if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2054(Parameters.CodeVerifier),
|
|
uri: SR.FormatID8000(SR.ID2054));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that use an authorization code,
|
|
/// a device code or a refresh token that was issued for a different client application.
|
|
/// </summary>
|
|
public sealed class ValidatePresenters : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidatePresenters>()
|
|
.SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() &&
|
|
!context.Request.IsDeviceCodeGrantType() &&
|
|
!context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
var presenters = context.Principal.GetPresenters();
|
|
if (presenters.IsDefaultOrEmpty)
|
|
{
|
|
// Note: presenters may be empty during a grant_type=refresh_token request if the refresh token
|
|
// was issued to a public client but cannot be null for an authorization or device code grant request.
|
|
if (context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0043));
|
|
}
|
|
|
|
if (context.Request.IsDeviceCodeGrantType())
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0044));
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
// If at least one presenter was associated to the authorization code/device code/refresh token,
|
|
// reject the request if the client_id of the caller cannot be retrieved or inferred.
|
|
if (string.IsNullOrEmpty(context.ClientId))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6090));
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2066) :
|
|
context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2067) :
|
|
SR.GetResourceString(SR.ID2068),
|
|
uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2066) :
|
|
context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2067) :
|
|
SR.FormatID8000(SR.ID2068));
|
|
|
|
return default;
|
|
}
|
|
|
|
// Ensure the authorization code/device code/refresh token was issued to the client making the token request.
|
|
// Note: when using the refresh token grant, client_id is optional but MUST be validated if present.
|
|
// See https://tools.ietf.org/html/rfc6749#section-6
|
|
// and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken.
|
|
if (!presenters.Contains(context.ClientId))
|
|
{
|
|
context.Logger.LogWarning(SR.GetResourceString(SR.ID6091));
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2069) :
|
|
context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2070) :
|
|
SR.GetResourceString(SR.ID2071),
|
|
uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2069) :
|
|
context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2070) :
|
|
SR.FormatID8000(SR.ID2071));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that specify an invalid redirect_uri.
|
|
/// </summary>
|
|
public sealed class ValidateRedirectUri : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateRedirectUri>()
|
|
.SetOrder(ValidatePresenters.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Validate the redirect_uri sent by the client application as part of this token request.
|
|
// Note: for pure OAuth 2.0 requests, redirect_uri is only mandatory if the authorization request
|
|
// contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri
|
|
// but the specifications allow proceeding the token request without returning an error
|
|
// if the authorization request didn't contain an explicit redirect_uri.
|
|
// See https://tools.ietf.org/html/rfc6749#section-4.1.3
|
|
// and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation.
|
|
var uri = context.Principal.GetClaim(Claims.Private.RedirectUri);
|
|
if (string.IsNullOrEmpty(uri))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.Request.RedirectUri))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RedirectUri);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.RedirectUri),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
if (!string.Equals(uri, context.Request.RedirectUri, StringComparison.Ordinal))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.RedirectUri);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: SR.FormatID2072(Parameters.RedirectUri),
|
|
uri: SR.FormatID8000(SR.ID2072));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting token requests that specify an invalid code verifier.
|
|
/// </summary>
|
|
public sealed class ValidateCodeVerifier : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateCodeVerifier>()
|
|
.SetOrder(ValidateRedirectUri.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Note: the ValidateProofKeyForCodeExchangeRequirement handler (invoked earlier) ensures
|
|
// a code_verifier is specified if the proof key for code exchange requirement was enforced
|
|
// for the client application. But unlike the aforementioned handler, ValidateCodeVerifier
|
|
// is active even if the degraded mode is enabled and ensures that a code_verifier is sent if a
|
|
// code_challenge was stored in the authorization code when the authorization request was handled.
|
|
|
|
var challenge = context.Principal.GetClaim(Claims.Private.CodeChallenge);
|
|
if (string.IsNullOrEmpty(challenge))
|
|
{
|
|
// Validate that the token request does not include a code_verifier parameter
|
|
// when no code_challenge private claim was attached to the authorization code.
|
|
if (!string.IsNullOrEmpty(context.Request.CodeVerifier))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6093), Parameters.CodeVerifier);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2073(Parameters.CodeVerifier, Parameters.CodeChallenge),
|
|
uri: SR.FormatID8000(SR.ID2073));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
// Get the code verifier from the token request. If it cannot be found, return an invalid_grant error.
|
|
if (string.IsNullOrEmpty(context.Request.CodeVerifier))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: SR.FormatID2029(Parameters.CodeVerifier),
|
|
uri: SR.FormatID8000(SR.ID2029));
|
|
|
|
return default;
|
|
}
|
|
|
|
var comparand = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod) switch
|
|
{
|
|
// Note: when using the "plain" code challenge method, no hashing is actually performed.
|
|
// In this case, the raw bytes of the verifier are directly compared to the challenge.
|
|
CodeChallengeMethods.Plain => context.Request.CodeVerifier,
|
|
|
|
CodeChallengeMethods.Sha256 => Base64UrlEncoder.Encode(
|
|
OpenIddictHelpers.ComputeSha256Hash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier))),
|
|
|
|
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)),
|
|
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0045))
|
|
};
|
|
|
|
// Compare the verifier and the code challenge: if the two don't match, return an error.
|
|
// Note: to prevent timing attacks, a time-constant comparer is always used.
|
|
if (!OpenIddictHelpers.FixedTimeEquals(
|
|
left: MemoryMarshal.AsBytes(comparand.AsSpan()),
|
|
right: MemoryMarshal.AsBytes(challenge.AsSpan())))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: SR.FormatID2052(Parameters.CodeVerifier),
|
|
uri: SR.FormatID8000(SR.ID2052));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for rejecting refresh token requests that specify scopes
|
|
/// that were not initially granted by the resource owner during the authorization request.
|
|
/// </summary>
|
|
public sealed class ValidateGrantedScopes : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidateGrantedScopes>()
|
|
.SetOrder(ValidateCodeVerifier.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ValidateTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.Request.Scope) || !context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// When an explicit scope parameter has been included in the token request
|
|
// but was missing from the initial request, the request MUST be rejected.
|
|
// See http://tools.ietf.org/html/rfc6749#section-6 for more information.
|
|
var scopes = context.Principal.GetScopes().ToHashSet(StringComparer.Ordinal);
|
|
if (scopes.Count is 0)
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6094), Parameters.Scope);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: SR.FormatID2074(Parameters.Scope),
|
|
uri: SR.FormatID8000(SR.ID2074));
|
|
|
|
return default;
|
|
}
|
|
|
|
// When an explicit scope parameter has been included in the token request,
|
|
// the authorization server MUST ensure that it doesn't contain scopes
|
|
// that were not granted during the initial authorization/token request.
|
|
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
|
|
else if (!scopes.IsSupersetOf(context.Request.GetScopes()))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6095), Parameters.Scope);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: SR.FormatID2052(Parameters.Scope),
|
|
uri: SR.FormatID8000(SR.ID2052));
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the principal extracted
|
|
/// from the authorization code/refresh token to the event context.
|
|
/// </summary>
|
|
public sealed class AttachPrincipal : IOpenIddictServerHandler<HandleTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleTokenRequestContext>()
|
|
.UseSingletonHandler<AttachPrincipal>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleTokenRequestContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var notification = context.Transaction.GetProperty<ValidateTokenRequestContext>(
|
|
typeof(ValidateTokenRequestContext).FullName!) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
|
|
|
|
context.Principal ??= notification.Principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for converting token errors to standard invalid_grant responses.
|
|
/// </summary>
|
|
public sealed class NormalizeErrorResponse : IOpenIddictServerHandler<ApplyTokenResponseContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyTokenResponseContext>()
|
|
.UseSingletonHandler<NormalizeErrorResponse>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ApplyTokenResponseContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.Error))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// If the error indicates an invalid token caused by an invalid authorization,
|
|
// device code or refresh token, return a standard invalid_grant.
|
|
|
|
if (context.Request is null || !(context.Request.IsAuthorizationCodeGrantType() ||
|
|
context.Request.IsDeviceCodeGrantType() ||
|
|
context.Request.IsRefreshTokenGrantType()))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
context.Response.Error = context.Error switch
|
|
{
|
|
// Keep "expired_token" errors as-is if the request is a device code token request.
|
|
Errors.ExpiredToken when context.Request.IsDeviceCodeGrantType() => Errors.ExpiredToken,
|
|
|
|
// Convert "invalid_token" errors to "invalid_grant".
|
|
Errors.InvalidToken => Errors.InvalidGrant,
|
|
|
|
_ => context.Error // Otherwise, keep the error as-is.
|
|
};
|
|
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|