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.
1654 lines
82 KiB
1654 lines
82 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.Runtime.CompilerServices;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OpenIddict.Abstractions;
|
|
using static OpenIddict.Abstractions.OpenIddictConstants;
|
|
using static OpenIddict.Server.OpenIddictServerEvents;
|
|
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
|
|
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
|
|
|
|
namespace OpenIddict.Server
|
|
{
|
|
public static partial class OpenIddictServerHandlers
|
|
{
|
|
public static class Exchange
|
|
{
|
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
|
|
/*
|
|
* Token request top-level processing:
|
|
*/
|
|
ExtractTokenRequest.Descriptor,
|
|
ValidateTokenRequest.Descriptor,
|
|
HandleTokenRequest.Descriptor,
|
|
ApplyTokenResponse<ProcessChallengeContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessErrorResponseContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessRequestContext>.Descriptor,
|
|
ApplyTokenResponse<ProcessSigninContext>.Descriptor,
|
|
|
|
/*
|
|
* Token request validation:
|
|
*/
|
|
ValidateGrantType.Descriptor,
|
|
ValidateClientIdParameter.Descriptor,
|
|
ValidateAuthorizationCodeParameter.Descriptor,
|
|
ValidateClientCredentialsParameters.Descriptor,
|
|
ValidateRefreshTokenParameter.Descriptor,
|
|
ValidatePasswordParameters.Descriptor,
|
|
ValidateScopes.Descriptor,
|
|
ValidateClientId.Descriptor,
|
|
ValidateClientType.Descriptor,
|
|
ValidateClientSecret.Descriptor,
|
|
ValidateEndpointPermissions.Descriptor,
|
|
ValidateGrantTypePermissions.Descriptor,
|
|
ValidateScopePermissions.Descriptor,
|
|
ValidateToken.Descriptor,
|
|
ValidatePresenters.Descriptor,
|
|
ValidateRedirectUri.Descriptor,
|
|
ValidateCodeVerifier.Descriptor,
|
|
ValidateGrantedScopes.Descriptor,
|
|
|
|
/*
|
|
* Token request handling:
|
|
*/
|
|
AttachPrincipal.Descriptor);
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ExtractTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerProvider _provider;
|
|
|
|
public ExtractTokenRequest([NotNull] IOpenIddictServerProvider provider)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ExtractTokenRequest>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.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));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Token)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ExtractTokenRequestContext(context.Transaction);
|
|
await _provider.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(new StringBuilder()
|
|
.Append("The token request was not correctly extracted. To extract token requests, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<ExtractTokenRequestContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.ToString());
|
|
}
|
|
|
|
context.Logger.LogInformation("The token request was successfully extracted: {Request}.", notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ValidateTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerProvider _provider;
|
|
|
|
public ValidateTokenRequest([NotNull] IOpenIddictServerProvider provider)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ValidateTokenRequest>()
|
|
.SetOrder(ExtractTokenRequest.Descriptor.Order + 1_000)
|
|
.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));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Token)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenRequestContext(context.Transaction);
|
|
await _provider.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;
|
|
}
|
|
|
|
// Store the security principal extracted from the authorization code/refresh token as an environment property.
|
|
context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
|
|
|
|
context.Logger.LogInformation("The token request was successfully validated.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of handling token requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class HandleTokenRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerProvider _provider;
|
|
|
|
public HandleTokenRequest([NotNull] IOpenIddictServerProvider provider)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<HandleTokenRequest>()
|
|
.SetOrder(ValidateTokenRequest.Descriptor.Order + 1_000)
|
|
.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));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Token)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new HandleTokenRequestContext(context.Transaction);
|
|
await _provider.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.Principal != null)
|
|
{
|
|
var @event = new ProcessSigninContext(context.Transaction)
|
|
{
|
|
Principal = notification.Principal,
|
|
Response = new OpenIddictResponse()
|
|
};
|
|
|
|
await _provider.DispatchAsync(@event);
|
|
|
|
if (@event.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (@event.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException(new StringBuilder()
|
|
.Append("The token request was not handled. To handle token requests, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<HandleTokenRequestContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.Append("Alternatively, enable the pass-through mode to handle them at a later stage.")
|
|
.ToString());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ApplyTokenResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerProvider _provider;
|
|
|
|
public ApplyTokenResponse([NotNull] IOpenIddictServerProvider provider)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
|
|
.UseScopedHandler<ApplyTokenResponse<TContext>>()
|
|
.SetOrder(int.MaxValue - 100_000)
|
|
.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));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Token)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ApplyTokenResponseContext(context.Transaction);
|
|
await _provider.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that specify an invalid grant type.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Reject token requests missing the mandatory grant_type parameter.
|
|
if (string.IsNullOrEmpty(context.Request.GrantType))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the grant type was missing.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'grant_type' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
// Reject token requests that don't specify a supported grant type.
|
|
if (!context.Options.GrantTypes.Contains(context.Request.GrantType))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the '{GrantType}' " +
|
|
"grant type is not supported.", context.Request.GrantType);
|
|
|
|
context.Reject(
|
|
error: Errors.UnsupportedGrantType,
|
|
description: "The specified 'grant_type' parameter is not supported.");
|
|
|
|
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: "The 'offline_access' scope is not allowed.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that don't specify a client identifier.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(context.ClientId))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// At this stage, reject the token request unless the client identification requirement was disabled.
|
|
// Independently of this setting, also reject grant_type=authorization_code requests that don't specify
|
|
// a client_id, 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.Options.AcceptAnonymousClients || context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'client_id' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that don't
|
|
/// specify an authorization code for the authorization code grant type.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == 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.LogError("The token request was rejected because the authorization code was missing.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'code' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that don't
|
|
/// specify client credentials for the client credentials grant type.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// 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.ClientId) ||
|
|
string.IsNullOrEmpty(context.Request.ClientSecret)))
|
|
{
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The 'client_id' and 'client_secret' parameters are " +
|
|
"required when using the client credentials grant.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that
|
|
/// specify invalid parameters for the refresh token grant type.
|
|
/// </summary>
|
|
public 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(ValidateClientCredentialsParameters.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == 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.LogError("The token request was rejected because the refresh token was missing.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'refresh_token' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests
|
|
/// that specify invalid parameters for the password grant type.
|
|
/// </summary>
|
|
public class ValidatePasswordParameters : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseSingletonHandler<ValidatePasswordParameters>()
|
|
.SetOrder(ValidateRefreshTokenParameter.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == 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.LogError("The token request was rejected because the resource owner credentials were missing.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'username' and/or 'password' parameters are missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
|
|
/// Note: this handler is not used when the degraded mode is enabled or when scope validation is disabled.
|
|
/// </summary>
|
|
public class ValidateScopes : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictScopeManager _scopeManager;
|
|
|
|
public ValidateScopes() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
public ValidateScopes([NotNull] IOpenIddictScopeManager scopeManager)
|
|
=> _scopeManager = scopeManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireScopeValidationEnabled>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.UseScopedHandler<ValidateScopes>()
|
|
.SetOrder(ValidatePasswordParameters.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == 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().Except(context.Options.Scopes);
|
|
if (scopes.Count != 0)
|
|
{
|
|
await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray()))
|
|
{
|
|
scopes = scopes.Remove(await _scopeManager.GetNameAsync(scope));
|
|
}
|
|
}
|
|
|
|
// If at least one scope was not recognized, return an error.
|
|
if (scopes.Count != 0)
|
|
{
|
|
context.Logger.LogError("The token request was rejected because " +
|
|
"invalid scopes were specified: {Scopes}.", scopes);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidScope,
|
|
description: "The specified 'scope' parameter is not valid.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that use an invalid client_id.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class ValidateClientId : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateClientId() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
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<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.UseScopedHandler<ValidateClientId>()
|
|
.SetOrder(ValidateScopes.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext 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("The token request was rejected because the client " +
|
|
"application was not found: '{ClientId}'.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidClient,
|
|
description: "The specified 'client_id' parameter is invalid.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests made by applications
|
|
/// whose client type is not compatible with the requested grant type.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class ValidateClientType : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateClientType() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
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<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.UseScopedHandler<ValidateClientType>()
|
|
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new InvalidOperationException("The client application details cannot be found in the database.");
|
|
}
|
|
|
|
if (await _applicationManager.IsPublicAsync(application))
|
|
{
|
|
// Public applications are not allowed to use the client credentials grant.
|
|
if (context.Request.IsClientCredentialsGrantType())
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the public client application '{ClientId}' " +
|
|
"was not allowed to use the client credentials grant.", context.Request.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.UnauthorizedClient,
|
|
description: "The specified 'grant_type' parameter is not valid for this client application.");
|
|
|
|
return;
|
|
}
|
|
|
|
// Reject token requests containing a client_secret when the client is a public application.
|
|
if (!string.IsNullOrEmpty(context.ClientSecret))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the public application '{ClientId}' " +
|
|
"was not allowed to send a client secret.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The 'client_secret' parameter is not valid for this client application.");
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
|
|
if (string.IsNullOrEmpty(context.ClientSecret))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the confidential or hybrid application " +
|
|
"'{ClientId}' didn't specify a client secret.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidClient,
|
|
description: "The 'client_secret' parameter required for this client application is missing.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests specifying an invalid client secret.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class ValidateClientSecret : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateClientSecret() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
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<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.UseScopedHandler<ValidateClientSecret>()
|
|
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new InvalidOperationException("The client application details cannot be found in the database.");
|
|
}
|
|
|
|
// If the application is not a public client, validate the client secret.
|
|
if (!await _applicationManager.IsPublicAsync(application) &&
|
|
!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the confidential or hybrid application " +
|
|
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidClient,
|
|
description: "The specified client credentials are invalid.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of 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 class ValidateEndpointPermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateEndpointPermissions() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
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<ValidateTokenRequestContext>()
|
|
.AddFilter<RequireClientIdParameter>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireEndpointPermissionsEnabled>()
|
|
.UseScopedHandler<ValidateEndpointPermissions>()
|
|
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new InvalidOperationException("The client application details cannot be found in the database.");
|
|
}
|
|
|
|
// Reject the request if the application is not allowed to use the token endpoint.
|
|
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Token))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the application '{ClientId}' " +
|
|
"was not allowed to use the token endpoint.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.UnauthorizedClient,
|
|
description: "This client application is not allowed to use the token endpoint.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of 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 class ValidateGrantTypePermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateGrantTypePermissions() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
public ValidateGrantTypePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new InvalidOperationException("The client application details cannot be found in the database.");
|
|
}
|
|
|
|
// 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.LogError("The token request was rejected because the application '{ClientId}' was not allowed to " +
|
|
"use the specified grant type: {GrantType}.", context.ClientId, context.Request.GrantType);
|
|
|
|
context.Reject(
|
|
error: Errors.UnauthorizedClient,
|
|
description: "This client application is not allowed to use the specified grant type.");
|
|
|
|
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.LogError("The token request was rejected because the application '{ClientId}' " +
|
|
"was not allowed to request the 'offline_access' scope.", context.ClientId);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The client application is not allowed to use the 'offline_access' scope.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of 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 class ValidateScopePermissions : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
|
|
public ValidateScopePermissions() => throw new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
|
|
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
|
|
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
|
|
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
|
|
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
|
|
.ToString());
|
|
|
|
public ValidateScopePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
|
|
=> _applicationManager = 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
|
|
if (application == null)
|
|
{
|
|
throw new InvalidOperationException("The client application details cannot be found in the database.");
|
|
}
|
|
|
|
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.LogError("The token request was rejected because the application '{ClientId}' " +
|
|
"was not allowed to use the scope {Scope}.", context.ClientId, scope);
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "This client application is not allowed to use the specified scope.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests
|
|
/// that don't specify a valid authorization code or refresh token.
|
|
/// </summary>
|
|
public class ValidateToken : IOpenIddictServerHandler<ValidateTokenRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerProvider _provider;
|
|
|
|
public ValidateToken([NotNull] IOpenIddictServerProvider provider)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
|
|
.UseScopedHandler<ValidateToken>()
|
|
.SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ProcessAuthenticationContext(context.Transaction);
|
|
await _provider.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 and store it as an environment property.
|
|
context.Principal = notification.Principal;
|
|
context.Transaction.Properties[Properties.AmbientPrincipal] = notification.Principal;
|
|
context.Transaction.Properties[Properties.OriginalPrincipal] = notification.Principal.Clone(_ => true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that use an authorization code
|
|
/// or a refresh token that was issued for a different client application.
|
|
/// </summary>
|
|
public 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(ValidateToken.Descriptor.Order + 1_000)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var presenters = context.Principal.GetPresenters();
|
|
if (presenters.Count == 0)
|
|
{
|
|
// 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 code grant request.
|
|
if (context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
throw new InvalidOperationException("The presenters list cannot be extracted from the authorization code.");
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
// If at least one presenter was associated to the authorization 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.LogError("The token request was rejected because the client identifier of the application " +
|
|
"was not available and could not be compared to the presenters list stored " +
|
|
"in the authorization code or the refresh token.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: context.Request.IsAuthorizationCodeGrantType() ?
|
|
"The specified authorization code cannot be used without specifying a client identifier." :
|
|
"The specified refresh token cannot be used without specifying a client identifier.");
|
|
|
|
return default;
|
|
}
|
|
|
|
// Ensure the authorization code/refresh token was issued to the client application 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.LogError("The token request was rejected because the authorization code " +
|
|
"or the refresh token was issued to a different client application.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: context.Request.IsAuthorizationCodeGrantType() ?
|
|
"The specified authorization code cannot be used by this client application." :
|
|
"The specified refresh token cannot be used by this client application.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that specify an invalid redirect_uri.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// 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 address = context.Principal.GetClaim(Claims.Private.RedirectUri);
|
|
if (string.IsNullOrEmpty(address))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.Request.RedirectUri))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " +
|
|
"parameter was missing from the grant_type=authorization_code request.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'redirect_uri' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
if (!string.Equals(address, context.Request.RedirectUri, StringComparison.Ordinal))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the 'redirect_uri' " +
|
|
"parameter didn't correspond to the expected value.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: "The specified 'redirect_uri' parameter doesn't match the client " +
|
|
"redirection endpoint the authorization code was initially sent to.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that specify an invalid code verifier.
|
|
/// </summary>
|
|
public 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)
|
|
.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] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// If a code challenge was initially sent in the authorization request and associated with the
|
|
// code, validate the code verifier to ensure the token request is sent by a legit caller.
|
|
var challenge = context.Principal.GetClaim(Claims.Private.CodeChallenge);
|
|
if (string.IsNullOrEmpty(challenge))
|
|
{
|
|
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.LogError("The token request was rejected because the required 'code_verifier' " +
|
|
"parameter was missing from the grant_type=authorization_code request.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidRequest,
|
|
description: "The mandatory 'code_verifier' parameter is missing.");
|
|
|
|
return default;
|
|
}
|
|
|
|
// If no code challenge method was specified, default to S256.
|
|
var method = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod);
|
|
if (string.IsNullOrEmpty(method))
|
|
{
|
|
method = CodeChallengeMethods.Sha256;
|
|
}
|
|
|
|
// Note: when using the "plain" code challenge method, no hashing is actually performed.
|
|
// In this case, the raw ASCII bytes of the verifier are directly compared to the challenge.
|
|
ReadOnlySpan<byte> data;
|
|
if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal))
|
|
{
|
|
data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier);
|
|
}
|
|
|
|
else if (string.Equals(method, CodeChallengeMethods.Sha256, StringComparison.Ordinal))
|
|
{
|
|
using var algorithm = SHA256.Create();
|
|
data = algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier));
|
|
}
|
|
|
|
else
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the 'code_challenge_method' was invalid.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: "The specified 'code_challenge_method' is invalid.");
|
|
|
|
return default;
|
|
}
|
|
|
|
// 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 (!FixedTimeEquals(data, Base64UrlEncoder.DecodeBytes(challenge)))
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: "The specified 'code_verifier' parameter is invalid.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
private static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
|
|
{
|
|
#if SUPPORTS_TIME_CONSTANT_COMPARISONS
|
|
return CryptographicOperations.FixedTimeEquals(left, right);
|
|
#else
|
|
// Note: these null checks can be theoretically considered as early checks
|
|
// (which would defeat the purpose of a time-constant comparison method),
|
|
// but the expected string length is the only information an attacker
|
|
// could get at this stage, which is not critical where this method is used.
|
|
|
|
if (left.Length != right.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var result = true;
|
|
|
|
for (var index = 0; index < left.Length; index++)
|
|
{
|
|
result &= left[index] == right[index];
|
|
}
|
|
|
|
return result;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting token requests that specify scopes that
|
|
/// were not initially granted by the resource owner during the authorization request.
|
|
/// </summary>
|
|
public 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)
|
|
.Build();
|
|
|
|
public ValueTask HandleAsync([NotNull] ValidateTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() || string.IsNullOrEmpty(context.Request.Scope))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// 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();
|
|
if (scopes.Count == 0)
|
|
{
|
|
context.Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: "The 'scope' parameter is not valid in this context.");
|
|
|
|
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 allowed 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.LogError("The token request was rejected because the 'scope' parameter was not valid.");
|
|
|
|
context.Reject(
|
|
error: Errors.InvalidGrant,
|
|
description: "The specified 'scope' parameter is invalid.");
|
|
|
|
return default;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the principal extracted
|
|
/// from the authorization code/refresh token to the event context.
|
|
/// </summary>
|
|
public 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)
|
|
.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] HandleTokenRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if (context.Transaction.Properties.TryGetValue(Properties.AmbientPrincipal, out var principal))
|
|
{
|
|
context.Principal ??= (ClaimsPrincipal) principal;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|