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.
1338 lines
63 KiB
1338 lines
63 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.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace OpenIddict.Server;
|
|
|
|
public static partial class OpenIddictServerHandlers
|
|
{
|
|
public static class Discovery
|
|
{
|
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } =
|
|
[
|
|
/*
|
|
* Configuration request top-level processing:
|
|
*/
|
|
ExtractConfigurationRequest.Descriptor,
|
|
ValidateConfigurationRequest.Descriptor,
|
|
HandleConfigurationRequest.Descriptor,
|
|
ApplyConfigurationResponse<ProcessErrorContext>.Descriptor,
|
|
ApplyConfigurationResponse<ProcessRequestContext>.Descriptor,
|
|
|
|
/*
|
|
* Configuration request handling:
|
|
*/
|
|
AttachIssuer.Descriptor,
|
|
AttachEndpoints.Descriptor,
|
|
AttachGrantTypes.Descriptor,
|
|
AttachResponseTypes.Descriptor,
|
|
AttachResponseModes.Descriptor,
|
|
AttachClientAuthenticationMethods.Descriptor,
|
|
AttachCodeChallengeMethods.Descriptor,
|
|
AttachScopes.Descriptor,
|
|
AttachClaims.Descriptor,
|
|
AttachSubjectTypes.Descriptor,
|
|
AttachPromptValues.Descriptor,
|
|
AttachSigningAlgorithms.Descriptor,
|
|
AttachSecurityRequirements.Descriptor,
|
|
AttachAdditionalMetadata.Descriptor,
|
|
|
|
/*
|
|
* JSON Web Key Set request top-level processing:
|
|
*/
|
|
ExtractJsonWebKeySetRequest.Descriptor,
|
|
ValidateJsonWebKeySetRequest.Descriptor,
|
|
HandleJsonWebKeySetRequest.Descriptor,
|
|
ApplyJsonWebKeySetResponse<ProcessErrorContext>.Descriptor,
|
|
ApplyJsonWebKeySetResponse<ProcessRequestContext>.Descriptor,
|
|
|
|
/*
|
|
* JSON Web Key Set request handling:
|
|
*/
|
|
AttachSigningKeys.Descriptor
|
|
];
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for extracting configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ExtractConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ExtractConfigurationRequest(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<RequireConfigurationRequest>()
|
|
.UseScopedHandler<ExtractConfigurationRequest>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ExtractConfigurationRequestContext(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.ID0037));
|
|
}
|
|
|
|
context.Logger.LogInformation(6066, SR.GetResourceString(SR.ID6066), notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for validating configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ValidateConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateConfigurationRequest(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<RequireConfigurationRequest>()
|
|
.UseScopedHandler<ValidateConfigurationRequest>()
|
|
.SetOrder(ExtractConfigurationRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ValidateConfigurationRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
context.Logger.LogInformation(6067, SR.GetResourceString(SR.ID6067));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for handling configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class HandleConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public HandleConfigurationRequest(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<RequireConfigurationRequest>()
|
|
.UseScopedHandler<HandleConfigurationRequest>()
|
|
.SetOrder(ValidateConfigurationRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new HandleConfigurationRequestContext(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;
|
|
}
|
|
|
|
var response = new OpenIddictResponse
|
|
{
|
|
[Metadata.Issuer] = notification.Issuer?.AbsoluteUri,
|
|
[Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint?.AbsoluteUri,
|
|
[Metadata.TokenEndpoint] = notification.TokenEndpoint?.AbsoluteUri,
|
|
[Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint?.AbsoluteUri,
|
|
[Metadata.EndSessionEndpoint] = notification.EndSessionEndpoint?.AbsoluteUri,
|
|
[Metadata.RevocationEndpoint] = notification.RevocationEndpoint?.AbsoluteUri,
|
|
[Metadata.UserInfoEndpoint] = notification.UserInfoEndpoint?.AbsoluteUri,
|
|
[Metadata.DeviceAuthorizationEndpoint] = notification.DeviceAuthorizationEndpoint?.AbsoluteUri,
|
|
[Metadata.PushedAuthorizationRequestEndpoint] = notification.PushedAuthorizationEndpoint?.AbsoluteUri,
|
|
[Metadata.MtlsEndpointAliases] = CreateMtlsEndpointAliases(notification),
|
|
[Metadata.JwksUri] = notification.JsonWebKeySetEndpoint?.AbsoluteUri,
|
|
[Metadata.GrantTypesSupported] = notification.GrantTypes.ToImmutableArray<string?>(),
|
|
[Metadata.ResponseTypesSupported] = notification.ResponseTypes.ToImmutableArray<string?>(),
|
|
[Metadata.ResponseModesSupported] = notification.ResponseModes.ToImmutableArray<string?>(),
|
|
[Metadata.ScopesSupported] = notification.Scopes.ToImmutableArray<string?>(),
|
|
[Metadata.ClaimsSupported] = notification.Claims.ToImmutableArray<string?>(),
|
|
[Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToImmutableArray<string?>(),
|
|
[Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToImmutableArray<string?>(),
|
|
[Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToImmutableArray<string?>(),
|
|
[Metadata.PromptValuesSupported] = notification.PromptValues.ToImmutableArray<string?>(),
|
|
[Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToImmutableArray<string?>(),
|
|
[Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToImmutableArray<string?>(),
|
|
[Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToImmutableArray<string?>(),
|
|
[Metadata.DeviceAuthorizationEndpointAuthMethodsSupported] = notification.DeviceAuthorizationEndpointAuthenticationMethods.ToImmutableArray<string?>(),
|
|
[Metadata.PushedAuthorizationRequestEndpointAuthMethodsSupported] = notification.PushedAuthorizationEndpointAuthenticationMethods.ToImmutableArray<string?>(),
|
|
[Metadata.RequirePushedAuthorizationRequests] = notification.RequirePushedAuthorizationRequests,
|
|
[Metadata.TlsClientCertificateBoundAccessTokens] = notification.TlsClientCertificateBoundAccessTokens
|
|
};
|
|
|
|
foreach (var metadata in notification.Metadata)
|
|
{
|
|
response.SetParameter(metadata.Key, metadata.Value);
|
|
}
|
|
|
|
context.Transaction.Response = response;
|
|
|
|
static JsonObject CreateMtlsEndpointAliases(HandleConfigurationRequestContext context)
|
|
{
|
|
var node = new JsonObject();
|
|
|
|
if (context.MtlsDeviceAuthorizationEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.DeviceAuthorizationEndpoint, context.MtlsDeviceAuthorizationEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
if (context.MtlsIntrospectionEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.IntrospectionEndpoint, context.MtlsIntrospectionEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
if (context.MtlsPushedAuthorizationEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.PushedAuthorizationRequestEndpoint, context.MtlsPushedAuthorizationEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
if (context.MtlsRevocationEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.RevocationEndpoint, context.MtlsRevocationEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
if (context.MtlsTokenEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.TokenEndpoint, context.MtlsTokenEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
if (context.MtlsUserInfoEndpointAlias is not null)
|
|
{
|
|
node.Add(Metadata.UserInfoEndpoint, context.MtlsUserInfoEndpointAlias.AbsoluteUri);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for processing configuration responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ApplyConfigurationResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ApplyConfigurationResponse(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<RequireConfigurationRequest>()
|
|
.UseScopedHandler<ApplyConfigurationResponse<TContext>>()
|
|
.SetOrder(500_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(TContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ApplyConfigurationResponseContext(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.ID0272));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the issuer to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachIssuer : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachIssuer>()
|
|
.SetOrder(int.MaxValue - 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.Issuer = (context.Options.Issuer ?? context.BaseUri) switch
|
|
{
|
|
{ IsAbsoluteUri: true } uri => uri,
|
|
|
|
// Throw an exception if the issuer cannot be retrieved or is not valid.
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0496))
|
|
};
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the endpoint URIs to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachEndpoints : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachEndpoints>()
|
|
.SetOrder(AttachIssuer.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
// Note: while OpenIddict allows specifying multiple endpoint URIs, the OAuth 2.0
|
|
// and OpenID Connect discovery specifications only allow a single URI per endpoint.
|
|
|
|
context.AuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.AuthorizationEndpointUris.FirstOrDefault());
|
|
|
|
context.DeviceAuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.DeviceAuthorizationEndpointUris.FirstOrDefault());
|
|
|
|
context.EndSessionEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.EndSessionEndpointUris.FirstOrDefault());
|
|
|
|
context.IntrospectionEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.IntrospectionEndpointUris.FirstOrDefault());
|
|
|
|
context.JsonWebKeySetEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.JsonWebKeySetEndpointUris.FirstOrDefault());
|
|
|
|
context.MtlsDeviceAuthorizationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsDeviceAuthorizationEndpointAliasUri);
|
|
|
|
context.MtlsIntrospectionEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsIntrospectionEndpointAliasUri);
|
|
|
|
context.MtlsPushedAuthorizationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsPushedAuthorizationEndpointAliasUri);
|
|
|
|
context.MtlsRevocationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsRevocationEndpointAliasUri);
|
|
|
|
context.MtlsTokenEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsTokenEndpointAliasUri);
|
|
|
|
context.MtlsUserInfoEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.MtlsUserInfoEndpointAliasUri);
|
|
|
|
context.PushedAuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.PushedAuthorizationEndpointUris.FirstOrDefault());
|
|
|
|
context.RevocationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.RevocationEndpointUris.FirstOrDefault());
|
|
|
|
context.TokenEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.TokenEndpointUris.FirstOrDefault());
|
|
|
|
context.UserInfoEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
|
|
context.BaseUri, context.Options.UserInfoEndpointUris.FirstOrDefault());
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported grant types to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachGrantTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachGrantTypes>()
|
|
.SetOrder(AttachEndpoints.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.GrantTypes.UnionWith(context.Options.GrantTypes);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported response types to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachResponseTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachResponseTypes>()
|
|
.SetOrder(AttachGrantTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.ResponseTypes.UnionWith(context.Options.ResponseTypes);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported response modes to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachResponseModes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachResponseModes>()
|
|
.SetOrder(AttachResponseTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
// Only include the response modes if at least one response type is returned.
|
|
if (context.ResponseTypes.Count is 0)
|
|
{
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
// Note: returning an access or identity token using the query response mode is explicitly disallowed.
|
|
//
|
|
// To ensure the query response mode is not returned unless at least one response type that doesn't
|
|
// include id_token or token is included in the server configuration, a manual check is done here.
|
|
if (context.Options.ResponseModes.Contains(ResponseModes.Query) &&
|
|
context.ResponseTypes
|
|
.Select(static types => types.Split(Separators.Space, StringSplitOptions.None).ToHashSet(StringComparer.Ordinal))
|
|
.Any(static types => !types.Contains(ResponseTypes.IdToken) && !types.Contains(ResponseTypes.Token)))
|
|
{
|
|
context.ResponseModes.Add(ResponseModes.Query);
|
|
}
|
|
|
|
context.ResponseModes.UnionWith(context.Options.ResponseModes.Where(
|
|
static mode => mode is not ResponseModes.Query));
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported client
|
|
/// authentication methods to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachClientAuthenticationMethods : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachClientAuthenticationMethods>()
|
|
.SetOrder(AttachResponseModes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
// Note: "device_authorization_endpoint_auth_methods_supported" is not a standard parameter
|
|
// but is supported by OpenIddict 4.3.0 and higher for consistency with the other endpoints.
|
|
if (context.DeviceAuthorizationEndpoint is not null)
|
|
{
|
|
context.DeviceAuthorizationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods);
|
|
}
|
|
|
|
if (context.IntrospectionEndpoint is not null)
|
|
{
|
|
context.IntrospectionEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods);
|
|
}
|
|
|
|
// Note: "pushed_authorization_request_endpoint_auth_methods_supported" is not a standard parameter
|
|
// but is supported by OpenIddict 6.1.0 and higher for consistency with the other endpoints.
|
|
if (context.PushedAuthorizationEndpoint is not null)
|
|
{
|
|
context.PushedAuthorizationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods);
|
|
}
|
|
|
|
if (context.RevocationEndpoint is not null)
|
|
{
|
|
context.RevocationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods);
|
|
}
|
|
|
|
if (context.TokenEndpoint is not null)
|
|
{
|
|
context.TokenEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods);
|
|
}
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported
|
|
/// code challenge methods to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachCodeChallengeMethods : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachCodeChallengeMethods>()
|
|
.SetOrder(AttachClientAuthenticationMethods.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
// Only include the code challenge methods if the authorization code grant type is enabled.
|
|
if (context.GrantTypes.Contains(GrantTypes.AuthorizationCode))
|
|
{
|
|
context.CodeChallengeMethods.UnionWith(context.Options.CodeChallengeMethods);
|
|
}
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported response types to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachScopes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachScopes>()
|
|
.SetOrder(AttachCodeChallengeMethods.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.Scopes.UnionWith(context.Options.Scopes);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported claims to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachClaims : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachClaims>()
|
|
.SetOrder(AttachScopes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.Claims.UnionWith(context.Options.Claims);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported subject types to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachSubjectTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachSubjectTypes>()
|
|
.SetOrder(AttachClaims.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.SubjectTypes.UnionWith(context.Options.SubjectTypes);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported prompt values to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachPromptValues : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachPromptValues>()
|
|
.SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.PromptValues.UnionWith(context.Options.PromptValues);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the supported signing algorithms to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachSigningAlgorithms : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachSigningAlgorithms>()
|
|
.SetOrder(AttachPromptValues.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
foreach (var credentials in context.Options.SigningCredentials)
|
|
{
|
|
// Try to resolve the JWA algorithm short name.
|
|
var algorithm = credentials.Algorithm switch
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature
|
|
=> SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature
|
|
=> SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature
|
|
=> SecurityAlgorithms.EcdsaSha512,
|
|
#endif
|
|
SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature
|
|
=> SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature
|
|
=> SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature
|
|
=> SecurityAlgorithms.RsaSha512,
|
|
|
|
SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha512,
|
|
|
|
_ => null
|
|
};
|
|
|
|
// If the algorithm cannot be resolved, ignore it.
|
|
if (string.IsNullOrEmpty(algorithm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
context.IdTokenSigningAlgorithms.Add(algorithm);
|
|
}
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the security requirements to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachSecurityRequirements : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachSecurityRequirements>()
|
|
.SetOrder(AttachSigningAlgorithms.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
context.RequirePushedAuthorizationRequests = context.Options.RequirePushedAuthorizationRequests;
|
|
context.TlsClientCertificateBoundAccessTokens = context.Options.UseClientCertificateBoundAccessTokens;
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching additional metadata to the provider discovery document.
|
|
/// </summary>
|
|
public sealed class AttachAdditionalMetadata : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachAdditionalMetadata>()
|
|
.SetOrder(AttachSecurityRequirements.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
// Note: these optional features are not yet supported by OpenIddict,
|
|
// so "false" is returned to encourage clients not to use them.
|
|
context.Metadata[Metadata.ClaimsParameterSupported] = false;
|
|
context.Metadata[Metadata.RequestParameterSupported] = false;
|
|
context.Metadata[Metadata.RequestUriParameterSupported] = false;
|
|
|
|
// As of 3.2.0, OpenIddict automatically returns an "iss" parameter containing its identity as
|
|
// part of authorization responses to help clients mitigate mix-up attacks. For more information,
|
|
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-iss-auth-resp-05.
|
|
context.Metadata[Metadata.AuthorizationResponseIssParameterSupported] = true;
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for extracting JSON Web Key Set requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ExtractJsonWebKeySetRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ExtractJsonWebKeySetRequest(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<RequireJsonWebKeySetRequest>()
|
|
.UseScopedHandler<ExtractJsonWebKeySetRequest>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ExtractJsonWebKeySetRequestContext(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.ID0038));
|
|
}
|
|
|
|
context.Logger.LogInformation(6068, SR.GetResourceString(SR.ID6068), notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for validating JSON Web Key Set requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ValidateJsonWebKeySetRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateJsonWebKeySetRequest(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<RequireJsonWebKeySetRequest>()
|
|
.UseScopedHandler<ValidateJsonWebKeySetRequest>()
|
|
.SetOrder(ExtractJsonWebKeySetRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ValidateJsonWebKeySetRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
context.Logger.LogInformation(6069, SR.GetResourceString(SR.ID6069));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for handling JSON Web Key Set requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class HandleJsonWebKeySetRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public HandleJsonWebKeySetRequest(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<RequireJsonWebKeySetRequest>()
|
|
.UseScopedHandler<HandleJsonWebKeySetRequest>()
|
|
.SetOrder(ValidateJsonWebKeySetRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new HandleJsonWebKeySetRequestContext(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;
|
|
}
|
|
|
|
using var stream = new MemoryStream();
|
|
using var writer = new Utf8JsonWriter(stream);
|
|
|
|
writer.WriteStartArray();
|
|
|
|
foreach (var key in notification.Keys)
|
|
{
|
|
// Ensure a key type has been provided.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.1
|
|
if (string.IsNullOrEmpty(key.Kty))
|
|
{
|
|
context.Logger.LogWarning(6070, SR.GetResourceString(SR.ID6070), JsonWebKeyParameterNames.Kty);
|
|
|
|
continue;
|
|
}
|
|
|
|
writer.WriteStartObject();
|
|
|
|
if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid);
|
|
if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use);
|
|
if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty);
|
|
if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg);
|
|
if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv);
|
|
if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E);
|
|
if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N);
|
|
if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X);
|
|
if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y);
|
|
if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t);
|
|
if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u);
|
|
|
|
if (key.KeyOps.Count is not 0)
|
|
{
|
|
writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps);
|
|
writer.WriteStartArray();
|
|
|
|
for (var index = 0; index < key.KeyOps.Count; index++)
|
|
{
|
|
writer.WriteStringValue(key.KeyOps[index]);
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
}
|
|
|
|
if (key.X5c.Count is not 0)
|
|
{
|
|
writer.WritePropertyName(JsonWebKeyParameterNames.X5c);
|
|
writer.WriteStartArray();
|
|
|
|
for (var index = 0; index < key.X5c.Count; index++)
|
|
{
|
|
writer.WriteStringValue(key.X5c[index]);
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
}
|
|
|
|
writer.WriteEndObject();
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
writer.Flush();
|
|
stream.Seek(0L, SeekOrigin.Begin);
|
|
|
|
using var document = JsonDocument.Parse(stream);
|
|
|
|
// Note: AddParameter() is used here to ensure the mandatory "keys" node
|
|
// is returned to the caller, even if the key set doesn't expose any key.
|
|
// See https://tools.ietf.org/html/rfc7517#section-5 for more information.
|
|
var response = new OpenIddictResponse();
|
|
response.AddParameter(Parameters.Keys, document.RootElement.Clone());
|
|
|
|
context.Transaction.Response = response;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for processing JSON Web Key Set responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public sealed class ApplyJsonWebKeySetResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ApplyJsonWebKeySetResponse(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<RequireJsonWebKeySetRequest>()
|
|
.UseScopedHandler<ApplyJsonWebKeySetResponse<TContext>>()
|
|
.SetOrder(500_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(TContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
var notification = new ApplyJsonWebKeySetResponseContext(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.ID0039));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible for attaching the signing keys to the JSON Web Key Set document.
|
|
/// </summary>
|
|
public sealed class AttachSigningKeys : IOpenIddictServerHandler<HandleJsonWebKeySetRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleJsonWebKeySetRequestContext>()
|
|
.UseSingletonHandler<AttachSigningKeys>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(HandleJsonWebKeySetRequestContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
foreach (var credentials in context.Options.SigningCredentials)
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
|
|
{
|
|
context.Logger.LogInformation(6071, SR.GetResourceString(SR.ID6071), credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
#else
|
|
if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256))
|
|
{
|
|
context.Logger.LogInformation(6072, SR.GetResourceString(SR.ID6072), credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
var key = new JsonWebKey
|
|
{
|
|
Use = JsonWebKeyUseNames.Sig,
|
|
|
|
// Resolve the JWA identifier from the algorithm specified in the credentials.
|
|
Alg = credentials.Algorithm switch
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature
|
|
=> SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature
|
|
=> SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature
|
|
=> SecurityAlgorithms.EcdsaSha512,
|
|
#endif
|
|
SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature
|
|
=> SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature
|
|
=> SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature
|
|
=> SecurityAlgorithms.RsaSha512,
|
|
|
|
SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature
|
|
=> SecurityAlgorithms.RsaSsaPssSha512,
|
|
|
|
_ => null
|
|
},
|
|
|
|
// Use the key identifier specified in the signing credentials.
|
|
Kid = credentials.Kid
|
|
};
|
|
|
|
if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256))
|
|
{
|
|
// Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm
|
|
// from a generic asymmetric security key. To work around this limitation, try to cast
|
|
// the security key to the built-in IdentityModel types to extract the required RSA instance.
|
|
// See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395.
|
|
|
|
var parameters = credentials.Key switch
|
|
{
|
|
X509SecurityKey { PublicKey: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false),
|
|
|
|
RsaSecurityKey { Rsa: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false),
|
|
RsaSecurityKey { Parameters: RSAParameters value } => value,
|
|
|
|
_ => (RSAParameters?) null
|
|
};
|
|
|
|
if (parameters is null)
|
|
{
|
|
context.Logger.LogWarning(6073, SR.GetResourceString(SR.ID6073), credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
|
|
Debug.Assert(parameters.Value.Exponent is not null &&
|
|
parameters.Value.Modulus is not null, SR.GetResourceString(SR.ID4003));
|
|
|
|
key.Kty = JsonWebAlgorithmsKeyTypes.RSA;
|
|
|
|
// Note: both E and N must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7518#section-6.3.1.1.
|
|
key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent);
|
|
key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus);
|
|
}
|
|
|
|
#if SUPPORTS_ECDSA
|
|
else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
|
|
{
|
|
var parameters = credentials.Key switch
|
|
{
|
|
X509SecurityKey { PublicKey: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false),
|
|
|
|
ECDsaSecurityKey { ECDsa: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false),
|
|
|
|
_ => (ECParameters?) null
|
|
};
|
|
|
|
if (parameters is null)
|
|
{
|
|
context.Logger.LogWarning(6074, SR.GetResourceString(SR.ID6074), credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
|
|
var curve =
|
|
OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 :
|
|
OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 :
|
|
OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null;
|
|
|
|
if (string.IsNullOrEmpty(curve))
|
|
{
|
|
context.Logger.LogWarning(6167, SR.GetResourceString(SR.ID6167), credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
|
|
Debug.Assert(parameters.Value.Q.X is not null &&
|
|
parameters.Value.Q.Y is not null, SR.GetResourceString(SR.ID4004));
|
|
|
|
Debug.Assert(parameters.Value.Curve.Oid is not null, SR.GetResourceString(SR.ID4011));
|
|
Debug.Assert(parameters.Value.Curve.IsNamed, SR.GetResourceString(SR.ID4005));
|
|
|
|
key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve;
|
|
key.Crv = curve;
|
|
|
|
// Note: both X and Y must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7518#section-6.2.1.2.
|
|
key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X);
|
|
key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y);
|
|
}
|
|
#endif
|
|
|
|
// If the signing key is embedded in a X.509 certificate, set
|
|
// the x5t and x5c parameters using the certificate details.
|
|
var certificate = (credentials.Key as X509SecurityKey)?.Certificate;
|
|
if (certificate is not null)
|
|
{
|
|
// x5t must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.8.
|
|
key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());
|
|
|
|
// x5t#S256 must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.9.
|
|
key.X5tS256 = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(certificate.RawData));
|
|
|
|
// Unlike E or N, the certificates contained in x5c
|
|
// must be base64-encoded and not base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.7.
|
|
key.X5c.Add(Convert.ToBase64String(certificate.RawData));
|
|
}
|
|
|
|
context.Keys.Add(key);
|
|
}
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|