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.
3049 lines
133 KiB
3049 lines
133 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.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
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 SR = OpenIddict.Abstractions.OpenIddictResources;
|
|
|
|
namespace OpenIddict.Server
|
|
{
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public static partial class OpenIddictServerHandlers
|
|
{
|
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
|
|
/*
|
|
* Authentication processing:
|
|
*/
|
|
ValidateAuthenticationDemand.Descriptor,
|
|
EvaluateValidatedTokens.Descriptor,
|
|
ResolveValidatedTokens.Descriptor,
|
|
ValidateAccessToken.Descriptor,
|
|
ValidateAuthorizationCode.Descriptor,
|
|
ValidateDeviceCode.Descriptor,
|
|
ValidateGenericToken.Descriptor,
|
|
ValidateIdentityToken.Descriptor,
|
|
ValidateRefreshToken.Descriptor,
|
|
ValidateUserCode.Descriptor,
|
|
|
|
/*
|
|
* Challenge processing:
|
|
*/
|
|
ValidateChallengeDemand.Descriptor,
|
|
AttachDefaultChallengeError.Descriptor,
|
|
RejectDeviceCodeEntry.Descriptor,
|
|
RejectUserCodeEntry.Descriptor,
|
|
|
|
/*
|
|
* Sign-in processing:
|
|
*/
|
|
ValidateSignInDemand.Descriptor,
|
|
RestoreInternalClaims.Descriptor,
|
|
AttachDefaultScopes.Descriptor,
|
|
AttachDefaultPresenters.Descriptor,
|
|
InferResources.Descriptor,
|
|
EvaluateGeneratedTokens.Descriptor,
|
|
AttachAuthorization.Descriptor,
|
|
|
|
PrepareAccessTokenPrincipal.Descriptor,
|
|
PrepareAuthorizationCodePrincipal.Descriptor,
|
|
PrepareDeviceCodePrincipal.Descriptor,
|
|
PrepareRefreshTokenPrincipal.Descriptor,
|
|
PrepareIdentityTokenPrincipal.Descriptor,
|
|
PrepareUserCodePrincipal.Descriptor,
|
|
|
|
RedeemTokenEntry.Descriptor,
|
|
|
|
GenerateAccessToken.Descriptor,
|
|
GenerateAuthorizationCode.Descriptor,
|
|
GenerateDeviceCode.Descriptor,
|
|
GenerateRefreshToken.Descriptor,
|
|
|
|
AttachDeviceCodeIdentifier.Descriptor,
|
|
UpdateReferenceDeviceCodeEntry.Descriptor,
|
|
AttachTokenDigests.Descriptor,
|
|
|
|
GenerateUserCode.Descriptor,
|
|
GenerateIdentityToken.Descriptor,
|
|
|
|
AttachSignInParameters.Descriptor,
|
|
|
|
/*
|
|
* Sign-out processing:
|
|
*/
|
|
ValidateSignOutDemand.Descriptor,
|
|
AttachSignOutParameters.Descriptor)
|
|
|
|
.AddRange(Authentication.DefaultHandlers)
|
|
.AddRange(Device.DefaultHandlers)
|
|
.AddRange(Discovery.DefaultHandlers)
|
|
.AddRange(Exchange.DefaultHandlers)
|
|
.AddRange(Introspection.DefaultHandlers)
|
|
.AddRange(Protection.DefaultHandlers)
|
|
.AddRange(Revocation.DefaultHandlers)
|
|
.AddRange(Session.DefaultHandlers)
|
|
.AddRange(Userinfo.DefaultHandlers);
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting authentication demands made from unsupported endpoints.
|
|
/// </summary>
|
|
public class ValidateAuthenticationDemand : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.UseSingletonHandler<ValidateAuthenticationDemand>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
switch (context.EndpointType)
|
|
{
|
|
case OpenIddictServerEndpointType.Authorization:
|
|
case OpenIddictServerEndpointType.Introspection:
|
|
case OpenIddictServerEndpointType.Logout:
|
|
case OpenIddictServerEndpointType.Revocation:
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType():
|
|
case OpenIddictServerEndpointType.Userinfo:
|
|
case OpenIddictServerEndpointType.Verification:
|
|
return default;
|
|
|
|
case OpenIddictServerEndpointType.Token:
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0001));
|
|
|
|
default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0002));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of selecting the token types that should be validated.
|
|
/// </summary>
|
|
public class EvaluateValidatedTokens : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.UseSingletonHandler<EvaluateValidatedTokens>()
|
|
.SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
(context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch
|
|
{
|
|
// The userinfo endpoint requires sending a valid access token.
|
|
OpenIddictServerEndpointType.Userinfo => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateAuthorizationCode, context.RequireAuthorizationCode) = context.EndpointType switch
|
|
{
|
|
// The authorization code grant requires sending a valid authorization code.
|
|
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateDeviceCode, context.RequireDeviceCode) = context.EndpointType switch
|
|
{
|
|
// The device code grant requires sending a valid device code.
|
|
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateGenericToken, context.RequireGenericToken) = context.EndpointType switch
|
|
{
|
|
// Tokens received by the introspection and revocation endpoints can be of any type.
|
|
// Additional token type filtering is made by the endpoint themselves, if needed.
|
|
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateIdentityToken, context.RequireIdentityToken) = context.EndpointType switch
|
|
{
|
|
// The identity token received by the authorization and logout
|
|
// endpoints are not required and serve as optional hints.
|
|
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout => (true, false),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateRefreshToken, context.RequireRefreshToken) = context.EndpointType switch
|
|
{
|
|
// The refresh token grant requires sending a valid refresh token.
|
|
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.ValidateUserCode, context.RequireUserCode) = context.EndpointType switch
|
|
{
|
|
// Note: the verification endpoint can be accessed without specifying a
|
|
// user code (that can be later set by the user using a form, for instance).
|
|
OpenIddictServerEndpointType.Verification => (true, false),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of resolving the token from the incoming request.
|
|
/// </summary>
|
|
public class ResolveValidatedTokens : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.UseSingletonHandler<ResolveValidatedTokens>()
|
|
.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.AccessToken = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Userinfo when context.ValidateAccessToken => context.Request.AccessToken,
|
|
|
|
_ => null
|
|
};
|
|
|
|
context.AuthorizationCode = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Token when context.ValidateAuthorizationCode => context.Request.Code,
|
|
|
|
_ => null
|
|
};
|
|
|
|
context.DeviceCode = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Token when context.ValidateDeviceCode => context.Request.DeviceCode,
|
|
|
|
_ => null
|
|
};
|
|
|
|
(context.GenericToken, context.GenericTokenTypeHint) = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Introspection or
|
|
OpenIddictServerEndpointType.Revocation
|
|
when context.ValidateGenericToken => (context.Request.Token, context.Request.TokenTypeHint),
|
|
|
|
_ => (null, null)
|
|
};
|
|
|
|
context.IdentityToken = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Authorization or
|
|
OpenIddictServerEndpointType.Logout
|
|
when context.ValidateIdentityToken => context.Request.IdTokenHint,
|
|
|
|
_ => null
|
|
};
|
|
|
|
context.RefreshToken = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Token when context.ValidateRefreshToken => context.Request.RefreshToken,
|
|
|
|
_ => null
|
|
};
|
|
|
|
context.UserCode = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Verification when context.ValidateUserCode => context.Request.UserCode,
|
|
|
|
_ => null
|
|
};
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the access token resolved from the context.
|
|
/// </summary>
|
|
public class ValidateAccessToken : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateAccessToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireAccessTokenValidated>()
|
|
.UseScopedHandler<ValidateAccessToken>()
|
|
.SetOrder(ResolveValidatedTokens.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.AccessTokenPrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.AccessToken))
|
|
{
|
|
if (context.RequireAccessToken)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.AccessToken,
|
|
ValidTokenTypes = { TokenTypeHints.AccessToken }
|
|
};
|
|
|
|
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.AccessTokenPrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the authorization code resolved from the context.
|
|
/// </summary>
|
|
public class ValidateAuthorizationCode : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateAuthorizationCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireAuthorizationCodeValidated>()
|
|
.UseScopedHandler<ValidateAuthorizationCode>()
|
|
.SetOrder(ValidateAccessToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.AuthorizationCodePrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.AuthorizationCode))
|
|
{
|
|
if (context.RequireAuthorizationCode)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.AuthorizationCode,
|
|
ValidTokenTypes = { TokenTypeHints.AuthorizationCode }
|
|
};
|
|
|
|
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.AuthorizationCodePrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the device code resolved from the context.
|
|
/// </summary>
|
|
public class ValidateDeviceCode : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateDeviceCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireDeviceCodeValidated>()
|
|
.UseScopedHandler<ValidateDeviceCode>()
|
|
.SetOrder(ValidateAuthorizationCode.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.DeviceCodePrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.DeviceCode))
|
|
{
|
|
if (context.RequireDeviceCode)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.DeviceCode,
|
|
ValidTokenTypes = { TokenTypeHints.DeviceCode }
|
|
};
|
|
|
|
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.DeviceCodePrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating tokens of unknown types resolved from the context.
|
|
/// </summary>
|
|
public class ValidateGenericToken : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateGenericToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireGenericTokenValidated>()
|
|
.UseScopedHandler<ValidateGenericToken>()
|
|
.SetOrder(ValidateDeviceCode.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.GenericTokenPrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.GenericToken))
|
|
{
|
|
if (context.RequireGenericToken)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.GenericToken,
|
|
TokenTypeHint = context.GenericTokenTypeHint,
|
|
|
|
// By default, only access tokens and refresh tokens can be introspected/revoked but
|
|
// tokens received by the introspection and revocation endpoints can be of any type.
|
|
//
|
|
// Additional token type filtering is made by the endpoint themselves, if needed.
|
|
// As such, the valid token types list is deliberately left empty in this case.
|
|
ValidTokenTypes = { }
|
|
};
|
|
|
|
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.GenericTokenPrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the identity token resolved from the context.
|
|
/// </summary>
|
|
public class ValidateIdentityToken : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateIdentityToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireIdentityTokenValidated>()
|
|
.UseScopedHandler<ValidateIdentityToken>()
|
|
.SetOrder(ValidateGenericToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.IdentityTokenPrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.IdentityToken))
|
|
{
|
|
if (context.RequireIdentityToken)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
// Don't validate the lifetime of id_tokens used as id_token_hints.
|
|
DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or
|
|
OpenIddictServerEndpointType.Logout,
|
|
Token = context.IdentityToken,
|
|
ValidTokenTypes = { TokenTypeHints.IdToken }
|
|
};
|
|
|
|
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.IdentityTokenPrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the refresh token resolved from the context.
|
|
/// </summary>
|
|
public class ValidateRefreshToken : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateRefreshToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireRefreshTokenValidated>()
|
|
.UseScopedHandler<ValidateRefreshToken>()
|
|
.SetOrder(ValidateIdentityToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.RefreshTokenPrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.RefreshToken))
|
|
{
|
|
if (context.RequireRefreshToken)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.RefreshToken,
|
|
ValidTokenTypes = { TokenTypeHints.RefreshToken }
|
|
};
|
|
|
|
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.RefreshTokenPrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating the user code resolved from the context.
|
|
/// </summary>
|
|
public class ValidateUserCode : IOpenIddictServerHandler<ProcessAuthenticationContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateUserCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
|
|
.AddFilter<RequireUserCodeValidated>()
|
|
.UseScopedHandler<ValidateUserCode>()
|
|
.SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.UserCodePrincipal is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.UserCode))
|
|
{
|
|
if (context.RequireUserCode)
|
|
{
|
|
context.Reject(
|
|
error: Errors.MissingToken,
|
|
description: SR.GetResourceString(SR.ID2000),
|
|
uri: SR.FormatID8000(SR.ID2000));
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateTokenContext(context.Transaction)
|
|
{
|
|
Token = context.UserCode,
|
|
ValidTokenTypes = { TokenTypeHints.UserCode }
|
|
};
|
|
|
|
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.UserCodePrincipal = notification.Principal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting challenge demands made from unsupported endpoints.
|
|
/// </summary>
|
|
public class ValidateChallengeDemand : IOpenIddictServerHandler<ProcessChallengeContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
|
|
.UseSingletonHandler<ValidateChallengeDemand>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessChallengeContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or
|
|
OpenIddictServerEndpointType.Token or
|
|
OpenIddictServerEndpointType.Userinfo or
|
|
OpenIddictServerEndpointType.Verification))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0006));
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of ensuring that the challenge response contains an appropriate error.
|
|
/// </summary>
|
|
public class AttachDefaultChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
|
|
.UseSingletonHandler<AttachDefaultChallengeError>()
|
|
.SetOrder(ValidateChallengeDemand.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessChallengeContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.Response.Error ??= context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
|
|
=> Errors.AccessDenied,
|
|
|
|
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
|
|
OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess,
|
|
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
|
|
};
|
|
|
|
context.Response.ErrorDescription ??= context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
|
|
=> SR.GetResourceString(SR.ID2015),
|
|
|
|
OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024),
|
|
OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025),
|
|
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
|
|
};
|
|
|
|
context.Response.ErrorUri ??= context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
|
|
=> SR.FormatID8000(SR.ID2015),
|
|
|
|
OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024),
|
|
OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025),
|
|
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
|
|
};
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting the device code entry associated with the user code.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class RejectDeviceCodeEntry : IOpenIddictServerHandler<ProcessChallengeContext>
|
|
{
|
|
private readonly IOpenIddictTokenManager _tokenManager;
|
|
|
|
public RejectDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public RejectDeviceCodeEntry(IOpenIddictTokenManager tokenManager)
|
|
=> _tokenManager = tokenManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireTokenStorageEnabled>()
|
|
.UseScopedHandler<RejectDeviceCodeEntry>()
|
|
.SetOrder(AttachDefaultChallengeError.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessChallengeContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Verification)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
|
|
typeof(ProcessAuthenticationContext).FullName!) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
|
|
|
|
Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Extract the device code identifier from the user code principal.
|
|
var identifier = notification.UserCodePrincipal.GetClaim(Claims.Private.DeviceCodeId);
|
|
if (string.IsNullOrEmpty(identifier))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0008));
|
|
}
|
|
|
|
var token = await _tokenManager.FindByIdAsync(identifier);
|
|
if (token is not null)
|
|
{
|
|
await _tokenManager.TryRejectAsync(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of rejecting the user code entry, if applicable.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class RejectUserCodeEntry : IOpenIddictServerHandler<ProcessChallengeContext>
|
|
{
|
|
private readonly IOpenIddictTokenManager _tokenManager;
|
|
|
|
public RejectUserCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public RejectUserCodeEntry(IOpenIddictTokenManager tokenManager)
|
|
=> _tokenManager = tokenManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireTokenStorageEnabled>()
|
|
.UseScopedHandler<RejectUserCodeEntry>()
|
|
.SetOrder(RejectDeviceCodeEntry.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessChallengeContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Verification)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
|
|
typeof(ProcessAuthenticationContext).FullName!) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
|
|
|
|
Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Extract the device code identifier from the authentication principal.
|
|
var identifier = notification.UserCodePrincipal.GetTokenId();
|
|
if (string.IsNullOrEmpty(identifier))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0009));
|
|
}
|
|
|
|
var token = await _tokenManager.FindByIdAsync(identifier);
|
|
if (token is not null)
|
|
{
|
|
await _tokenManager.TryRejectAsync(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of ensuring that the sign-in demand
|
|
/// is compatible with the type of the endpoint that handled the request.
|
|
/// </summary>
|
|
public class ValidateSignInDemand : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<ValidateSignInDemand>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or
|
|
OpenIddictServerEndpointType.Device or
|
|
OpenIddictServerEndpointType.Token or
|
|
OpenIddictServerEndpointType.Verification))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0010));
|
|
}
|
|
|
|
if (context.Principal is not { Identity: ClaimsIdentity })
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0011));
|
|
}
|
|
|
|
// Note: sign-in operations triggered from the device endpoint can't be associated to specific users
|
|
// as users' identity is not known until they reach the verification endpoint and validate the user code.
|
|
// As such, the principal used in this case cannot contain an authenticated identity or a subject claim.
|
|
if (context.EndpointType == OpenIddictServerEndpointType.Device)
|
|
{
|
|
if (context.Principal.Identity.IsAuthenticated)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0012));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject)))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0013));
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
if (!context.Principal.Identity.IsAuthenticated)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0014));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject)))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0015));
|
|
}
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of re-attaching internal claims to the authentication principal.
|
|
/// </summary>
|
|
public class RestoreInternalClaims : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<RestoreInternalClaims>()
|
|
.SetOrder(ValidateSignInDemand.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
switch (context.EndpointType)
|
|
{
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType():
|
|
case OpenIddictServerEndpointType.Verification:
|
|
break;
|
|
|
|
default: return default;
|
|
}
|
|
|
|
var identity = (ClaimsIdentity) context.Principal.Identity;
|
|
|
|
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
|
|
typeof(ProcessAuthenticationContext).FullName!) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
|
|
|
|
var principal = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
|
|
=> notification.AuthorizationCodePrincipal,
|
|
|
|
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
|
|
=> notification.DeviceCodePrincipal,
|
|
|
|
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
|
|
=> notification.RefreshTokenPrincipal,
|
|
|
|
OpenIddictServerEndpointType.Verification => notification.UserCodePrincipal,
|
|
|
|
_ => null
|
|
};
|
|
|
|
if (principal is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// Restore the internal claims resolved from the token.
|
|
foreach (var claims in principal.Claims
|
|
.Where(claim => claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
|
|
.GroupBy(claim => claim.Type))
|
|
{
|
|
// If the specified principal already contains one claim of the iterated type, ignore them.
|
|
if (context.Principal.Claims.Any(claim => claim.Type == claims.Key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// When the request is a verification request, don't flow the scopes from the user code.
|
|
if (context.EndpointType == OpenIddictServerEndpointType.Verification &&
|
|
string.Equals(claims.Key, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
identity.AddClaims(claims);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching default scopes to the authentication principal.
|
|
/// </summary>
|
|
public class AttachDefaultScopes : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<AttachDefaultScopes>()
|
|
.SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
|
|
// Note: the application is allowed to specify a different "scopes": in this case,
|
|
// don't replace the "scopes" property stored in the authentication ticket.
|
|
if (!context.Principal.HasClaim(Claims.Private.Scope) && context.Request.HasScope(Scopes.OpenId))
|
|
{
|
|
context.Principal.SetScopes(Scopes.OpenId);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching default presenters to the authentication principal.
|
|
/// </summary>
|
|
public class AttachDefaultPresenters : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<AttachDefaultPresenters>()
|
|
.SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Add the validated client_id to the list of authorized presenters,
|
|
// unless the presenters were explicitly set by the developer.
|
|
if (!context.Principal.HasClaim(Claims.Private.Presenter) && !string.IsNullOrEmpty(context.ClientId))
|
|
{
|
|
context.Principal.SetPresenters(context.ClientId);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of inferring resources from the audience claims if necessary.
|
|
/// </summary>
|
|
public class InferResources : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<InferResources>()
|
|
.SetOrder(AttachDefaultPresenters.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// When a "resources" property cannot be found in the ticket, infer it from the "audiences" property.
|
|
if (context.Principal.HasClaim(Claims.Private.Audience) &&
|
|
!context.Principal.HasClaim(Claims.Private.Resource))
|
|
{
|
|
context.Principal.SetResources(context.Principal.GetAudiences());
|
|
}
|
|
|
|
// Reset the audiences collection, as it's later set, based on the token type.
|
|
context.Principal.SetAudiences(ImmutableArray.Create<string>());
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of selecting the token types that
|
|
/// should be generated and optionally returned in the response.
|
|
/// </summary>
|
|
public class EvaluateGeneratedTokens : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<EvaluateGeneratedTokens>()
|
|
.SetOrder(InferResources.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
(context.GenerateAccessToken, context.IncludeAccessToken) = context.EndpointType switch
|
|
{
|
|
// For authorization requests, generate and return an access token
|
|
// if a response type containing the "token" value was specified.
|
|
OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Token)
|
|
=> (true, true),
|
|
|
|
// For token requests, always generate and return an access token.
|
|
OpenIddictServerEndpointType.Token => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.GenerateAuthorizationCode, context.IncludeAuthorizationCode) = context.EndpointType switch
|
|
{
|
|
// For authorization requests, generate and return an authorization code
|
|
// if a response type containing the "code" value was specified.
|
|
OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Code)
|
|
=> (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.GenerateDeviceCode, context.IncludeDeviceCode) = context.EndpointType switch
|
|
{
|
|
// For device requests, always generate and return a device code.
|
|
OpenIddictServerEndpointType.Device => (true, true),
|
|
|
|
// Note: a device code is not directly returned by the verification endpoint (that generally
|
|
// returns an empty response or redirects the user agent to another page), but a device code
|
|
// must be generated to replace the payload of the device code initially returned to the client.
|
|
// In this case, the device code is not returned as part of the response but persisted in the DB.
|
|
OpenIddictServerEndpointType.Verification => (true, false),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.GenerateIdentityToken, context.IncludeIdentityToken) = context.EndpointType switch
|
|
{
|
|
// For authorization requests, generate and return an identity token if a response type
|
|
// containing code was specified and if the openid scope was explicitly or implicitly granted.
|
|
OpenIddictServerEndpointType.Authorization when
|
|
context.Principal.HasScope(Scopes.OpenId) &&
|
|
context.Request.HasResponseType(ResponseTypes.IdToken) => (true, true),
|
|
|
|
// For token requests, only generate and return an identity token if the openid scope was granted.
|
|
OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OpenId) => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.GenerateRefreshToken, context.IncludeRefreshToken) = context.EndpointType switch
|
|
{
|
|
// For token requests, allow a refresh token to be returned
|
|
// if the special offline_access protocol scope was granted.
|
|
OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OfflineAccess)
|
|
=> (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
(context.GenerateUserCode, context.IncludeUserCode) = context.EndpointType switch
|
|
{
|
|
// Only generate and return a user code if the request is a device authorization request.
|
|
OpenIddictServerEndpointType.Device => (true, true),
|
|
|
|
_ => (false, false)
|
|
};
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of creating an ad-hoc authorization, if necessary.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class AttachAuthorization : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
private readonly IOpenIddictAuthorizationManager _authorizationManager;
|
|
|
|
public AttachAuthorization() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public AttachAuthorization(
|
|
IOpenIddictApplicationManager applicationManager,
|
|
IOpenIddictAuthorizationManager authorizationManager)
|
|
{
|
|
_applicationManager = applicationManager;
|
|
_authorizationManager = authorizationManager;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireAuthorizationStorageEnabled>()
|
|
.UseScopedHandler<AttachAuthorization>()
|
|
.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// If no authorization code, device code or refresh token is returned, don't create an authorization.
|
|
if (!context.GenerateAuthorizationCode && !context.GenerateDeviceCode && !context.GenerateRefreshToken)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If an authorization identifier was explicitly specified, don't create an ad-hoc authorization.
|
|
if (!string.IsNullOrEmpty(context.Principal.GetAuthorizationId()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var descriptor = new OpenIddictAuthorizationDescriptor
|
|
{
|
|
CreationDate = DateTimeOffset.UtcNow,
|
|
Principal = context.Principal,
|
|
Status = Statuses.Valid,
|
|
Subject = context.Principal.GetClaim(Claims.Subject),
|
|
Type = AuthorizationTypes.AdHoc
|
|
};
|
|
|
|
descriptor.Scopes.UnionWith(context.Principal.GetScopes());
|
|
|
|
// If the client application is known, associate it to the authorization.
|
|
if (!string.IsNullOrEmpty(context.Request.ClientId))
|
|
{
|
|
var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId);
|
|
if (application is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
|
|
}
|
|
|
|
descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
|
|
}
|
|
|
|
var authorization = await _authorizationManager.CreateAsync(descriptor);
|
|
if (authorization is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0018));
|
|
}
|
|
|
|
var identifier = await _authorizationManager.GetIdAsync(authorization);
|
|
|
|
if (string.IsNullOrEmpty(context.Request.ClientId))
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6007), identifier);
|
|
}
|
|
|
|
else
|
|
{
|
|
context.Logger.LogInformation(SR.GetResourceString(SR.ID6008), context.Request.ClientId, identifier);
|
|
}
|
|
|
|
// Attach the unique identifier of the ad hoc authorization to the authentication principal
|
|
// so that it is attached to all the derived tokens, allowing batched revocations support.
|
|
context.Principal.SetAuthorizationId(identifier);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the access token, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareAccessTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireAccessTokenGenerated>()
|
|
.UseSingletonHandler<PrepareAccessTokenPrincipal>()
|
|
.SetOrder(AttachAuthorization.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Create a new principal containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never exclude the subject and authorization identifier claims.
|
|
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Never exclude the presenters and scope private claims.
|
|
if (string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Always exclude private claims, whose values must generally be kept secret.
|
|
if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Claims whose destination is not explicitly referenced or doesn't
|
|
// contain "access_token" are not included in the access token.
|
|
if (!claim.HasDestination(Destinations.AccessToken))
|
|
{
|
|
context.Logger.LogDebug(SR.GetResourceString(SR.ID6009), claim.Type);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Remove the destinations from the claim properties.
|
|
foreach (var claim in principal.Claims)
|
|
{
|
|
claim.Properties.Remove(Properties.Destinations);
|
|
}
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
|
|
// Set the audiences based on the resource claims stored in the principal.
|
|
principal.SetAudiences(context.Principal.GetResources());
|
|
|
|
// Store the client identifier in the public client_id claim, if available.
|
|
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
|
|
principal.SetClaim(Claims.ClientId, context.ClientId);
|
|
|
|
// When receiving a grant_type=refresh_token request, determine whether the client application
|
|
// requests a limited set of scopes and immediately replace the scopes collection if necessary.
|
|
if (context.EndpointType == OpenIddictServerEndpointType.Token &&
|
|
context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope))
|
|
{
|
|
var scopes = context.Request.GetScopes();
|
|
principal.SetScopes(scopes.Intersect(context.Principal.GetScopes()));
|
|
|
|
context.Logger.LogDebug(SR.GetResourceString(SR.ID6010), scopes);
|
|
}
|
|
|
|
context.AccessTokenPrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the authorization code, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireAuthorizationCodeGenerated>()
|
|
.UseSingletonHandler<PrepareAuthorizationCodePrincipal>()
|
|
.SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Create a new principal containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Other claims are always included in the authorization code, even private claims.
|
|
return true;
|
|
});
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
|
|
// Attach the redirect_uri to allow for later comparison when
|
|
// receiving a grant_type=authorization_code token request.
|
|
principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri);
|
|
|
|
// Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier
|
|
// handler to validate the code verifier sent by the client as part of the token request.
|
|
if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
|
|
{
|
|
principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge);
|
|
|
|
// Default to plain if no explicit code challenge method was specified.
|
|
principal.SetClaim(Claims.Private.CodeChallengeMethod,
|
|
!string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ?
|
|
context.Request.CodeChallengeMethod : CodeChallengeMethods.Plain);
|
|
}
|
|
|
|
// Attach the nonce so that it can be later returned by
|
|
// the token endpoint as part of the JWT identity token.
|
|
principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce);
|
|
|
|
context.AuthorizationCodePrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the device code, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareDeviceCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDeviceCodeGenerated>()
|
|
.UseSingletonHandler<PrepareDeviceCodePrincipal>()
|
|
.SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Create a new principal containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Other claims are always included in the device code, even private claims.
|
|
return true;
|
|
});
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
var lifetime = context.Principal.GetDeviceCodeLifetime() ?? context.Options.DeviceCodeLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
|
|
// Restore the device code internal token identifier from the principal
|
|
// resolved from the user code used in the user code verification request.
|
|
if (context.EndpointType == OpenIddictServerEndpointType.Verification)
|
|
{
|
|
principal.SetClaim(Claims.Private.TokenId, context.Principal.GetClaim(Claims.Private.DeviceCodeId));
|
|
}
|
|
|
|
context.DeviceCodePrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the refresh token, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireRefreshTokenGenerated>()
|
|
.UseSingletonHandler<PrepareRefreshTokenPrincipal>()
|
|
.SetOrder(PrepareDeviceCodePrincipal.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Create a new principal containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Other claims are always included in the refresh token, even private claims.
|
|
return true;
|
|
});
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
// When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed
|
|
// and must exactly match the expiration date of the refresh token used in the token request.
|
|
if (context.EndpointType == OpenIddictServerEndpointType.Token &&
|
|
context.Request.IsRefreshTokenGrantType() &&
|
|
context.Options.DisableSlidingRefreshTokenExpiration)
|
|
{
|
|
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
|
|
typeof(ProcessAuthenticationContext).FullName!) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
|
|
|
|
Debug.Assert(notification.RefreshTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
principal.SetExpirationDate(notification.RefreshTokenPrincipal.GetExpirationDate());
|
|
}
|
|
|
|
else
|
|
{
|
|
var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
}
|
|
|
|
context.RefreshTokenPrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the identity token, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireIdentityTokenGenerated>()
|
|
.UseSingletonHandler<PrepareIdentityTokenPrincipal>()
|
|
.SetOrder(PrepareRefreshTokenPrincipal.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Replace the principal by a new one containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never exclude the subject and authorization identifier claims.
|
|
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Always exclude private claims by default, whose values must generally be kept secret.
|
|
if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Claims whose destination is not explicitly referenced or doesn't
|
|
// contain "id_token" are not included in the identity token.
|
|
if (!claim.HasDestination(Destinations.IdentityToken))
|
|
{
|
|
context.Logger.LogDebug(SR.GetResourceString(SR.ID6011), claim.Type);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Remove the destinations from the claim properties.
|
|
foreach (var claim in principal.Claims)
|
|
{
|
|
claim.Properties.Remove(Properties.Destinations);
|
|
}
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(context.ClientId))
|
|
{
|
|
principal.SetAudiences(context.ClientId);
|
|
}
|
|
|
|
// Use the client_id as the authorized party, if available.
|
|
// See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information.
|
|
principal.SetClaim(Claims.AuthorizedParty, context.ClientId);
|
|
|
|
// If a nonce was present in the authorization request, it MUST be included in the id_token generated
|
|
// by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim.
|
|
// See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information.
|
|
principal.SetClaim(Claims.Nonce, context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Authorization => context.Request.Nonce,
|
|
OpenIddictServerEndpointType.Token => context.Principal.GetClaim(Claims.Private.Nonce),
|
|
|
|
_ => null
|
|
});
|
|
|
|
context.IdentityTokenPrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of preparing and attaching the claims principal
|
|
/// used to generate the user code, if one is going to be returned.
|
|
/// </summary>
|
|
public class PrepareUserCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireUserCodeGenerated>()
|
|
.UseSingletonHandler<PrepareUserCodePrincipal>()
|
|
.SetOrder(PrepareIdentityTokenPrincipal.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Create a new principal containing only the filtered claims.
|
|
// Actors identities are also filtered (delegation scenarios).
|
|
var principal = context.Principal.Clone(claim =>
|
|
{
|
|
// Never include the public or internal token identifiers to ensure the identifiers
|
|
// that are automatically inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never include the creation and expiration dates that are automatically
|
|
// inherited from the parent token are not reused for the new token.
|
|
if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Other claims are always included in the authorization code, even private claims.
|
|
return true;
|
|
});
|
|
|
|
principal.SetCreationDate(DateTimeOffset.UtcNow);
|
|
|
|
var lifetime = context.Principal.GetUserCodeLifetime() ?? context.Options.UserCodeLifetime;
|
|
if (lifetime.HasValue)
|
|
{
|
|
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
|
|
}
|
|
|
|
// Store the client_id as a public client_id claim.
|
|
principal.SetClaim(Claims.ClientId, context.Request.ClientId);
|
|
|
|
context.UserCodePrincipal = principal;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of redeeming the token entry corresponding to
|
|
/// the received authorization code, device code, user code or refresh token.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class RedeemTokenEntry : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictTokenManager _tokenManager;
|
|
|
|
public RedeemTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public RedeemTokenEntry(IOpenIddictTokenManager tokenManager)
|
|
=> _tokenManager = tokenManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireTokenStorageEnabled>()
|
|
.UseScopedHandler<RedeemTokenEntry>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
switch (context.EndpointType)
|
|
{
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType():
|
|
case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() &&
|
|
!context.Options.DisableRollingRefreshTokens:
|
|
case OpenIddictServerEndpointType.Verification:
|
|
break;
|
|
|
|
default: return;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
// Extract the token identifier from the authentication principal.
|
|
// If no token identifier can be found, this indicates that the token has no backing database entry.
|
|
var identifier = context.Principal.GetTokenId();
|
|
if (string.IsNullOrEmpty(identifier))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If rolling tokens are enabled or if the request is a a code or device code token request
|
|
// or a user code verification request, mark the token as redeemed to prevent future reuses.
|
|
var token = await _tokenManager.FindByIdAsync(identifier);
|
|
if (token is not null)
|
|
{
|
|
await _tokenManager.TryRedeemAsync(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating an access token for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateAccessToken : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateAccessToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireAccessTokenGenerated>()
|
|
.UseScopedHandler<GenerateAccessToken>()
|
|
.SetOrder(RedeemTokenEntry.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
// Access tokens can be converted to reference tokens if the
|
|
// corresponding option was enabled in the server options.
|
|
PersistTokenPayload = context.Options.UseReferenceAccessTokens,
|
|
Principal = context.AccessTokenPrincipal!,
|
|
TokenType = TokenTypeHints.AccessToken
|
|
};
|
|
|
|
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.AccessToken = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating an authorization code for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateAuthorizationCode : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateAuthorizationCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireAuthorizationCodeGenerated>()
|
|
.UseScopedHandler<GenerateAuthorizationCode>()
|
|
.SetOrder(GenerateAccessToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
PersistTokenPayload = !context.Options.DisableTokenStorage,
|
|
Principal = context.AuthorizationCodePrincipal!,
|
|
TokenType = TokenTypeHints.AuthorizationCode
|
|
};
|
|
|
|
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.AuthorizationCode = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating a device code for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateDeviceCode : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateDeviceCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDeviceCodeGenerated>()
|
|
.UseScopedHandler<GenerateDeviceCode>()
|
|
.SetOrder(GenerateAuthorizationCode.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
// Device codes can be converted to reference tokens if they are not generated
|
|
// as part of a device code swap made by the user code verification endpoint.
|
|
PersistTokenPayload = context.EndpointType switch
|
|
{
|
|
OpenIddictServerEndpointType.Verification => false,
|
|
|
|
_ => !context.Options.DisableTokenStorage
|
|
},
|
|
Principal = context.DeviceCodePrincipal!,
|
|
TokenType = TokenTypeHints.DeviceCode
|
|
};
|
|
|
|
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.DeviceCode = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating a refresh token for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateRefreshToken : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateRefreshToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireRefreshTokenGenerated>()
|
|
.UseScopedHandler<GenerateRefreshToken>()
|
|
.SetOrder(GenerateDeviceCode.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
// Refresh tokens can be converted to reference tokens if the
|
|
// corresponding option was enabled in the server options.
|
|
PersistTokenPayload = context.Options.UseReferenceRefreshTokens,
|
|
Principal = context.RefreshTokenPrincipal!,
|
|
TokenType = TokenTypeHints.RefreshToken
|
|
};
|
|
|
|
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.RefreshToken = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating and attaching the device code identifier to the user code principal.
|
|
/// </summary>
|
|
public class AttachDeviceCodeIdentifier : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDeviceCodeGenerated>()
|
|
.AddFilter<RequireUserCodeGenerated>()
|
|
.UseSingletonHandler<AttachDeviceCodeIdentifier>()
|
|
.SetOrder(GenerateRefreshToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.UserCodePrincipal is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0020));
|
|
}
|
|
|
|
var identifier = context.DeviceCodePrincipal?.GetTokenId();
|
|
if (!string.IsNullOrEmpty(identifier))
|
|
{
|
|
context.UserCodePrincipal.SetClaim(Claims.Private.DeviceCodeId, identifier);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of updating the existing reference device code entry.
|
|
/// Note: this handler is not used when the degraded mode is enabled.
|
|
/// </summary>
|
|
public class UpdateReferenceDeviceCodeEntry : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictTokenManager _tokenManager;
|
|
|
|
public UpdateReferenceDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
|
|
|
|
public UpdateReferenceDeviceCodeEntry(IOpenIddictTokenManager tokenManager)
|
|
=> _tokenManager = tokenManager;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireDegradedModeDisabled>()
|
|
.AddFilter<RequireTokenStorageEnabled>()
|
|
.AddFilter<RequireDeviceCodeGenerated>()
|
|
.UseScopedHandler<UpdateReferenceDeviceCodeEntry>()
|
|
.SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Verification || string.IsNullOrEmpty(context.DeviceCode))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
|
|
|
|
if (context.DeviceCodePrincipal is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0020));
|
|
}
|
|
|
|
// Extract the token identifier from the authentication principal.
|
|
var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId);
|
|
if (string.IsNullOrEmpty(identifier))
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0008));
|
|
}
|
|
|
|
var token = await _tokenManager.FindByIdAsync(identifier);
|
|
if (token is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0265));
|
|
}
|
|
|
|
// Replace the device code details by the payload derived from the new device code principal,
|
|
// that includes all the user claims populated by the application after authenticating the user.
|
|
var descriptor = new OpenIddictTokenDescriptor();
|
|
await _tokenManager.PopulateAsync(descriptor, token);
|
|
|
|
// Note: the lifetime is deliberately extended to give more time to the client to redeem the code.
|
|
descriptor.ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate();
|
|
descriptor.Payload = context.DeviceCode;
|
|
descriptor.Principal = context.DeviceCodePrincipal;
|
|
descriptor.Status = Statuses.Valid;
|
|
descriptor.Subject = context.DeviceCodePrincipal.GetClaim(Claims.Subject);
|
|
|
|
await _tokenManager.UpdateAsync(token, descriptor);
|
|
|
|
context.Logger.LogTrace(SR.GetResourceString(SR.ID6021), await _tokenManager.GetIdAsync(token));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating and attaching the hashes of
|
|
/// the access token and authorization code to the identity token principal.
|
|
/// </summary>
|
|
public class AttachTokenDigests : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireIdentityTokenGenerated>()
|
|
.UseSingletonHandler<AttachTokenDigests>()
|
|
.SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.IdentityTokenPrincipal is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(context.AccessToken) && string.IsNullOrEmpty(context.AuthorizationCode))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var credentials = context.Options.SigningCredentials.FirstOrDefault(
|
|
credentials => credentials.Key is AsymmetricSecurityKey);
|
|
if (credentials is null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0266));
|
|
}
|
|
|
|
using var hash = GetHashAlgorithm(credentials);
|
|
if (hash is null || hash is KeyedHashAlgorithm)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0267));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(context.AccessToken))
|
|
{
|
|
var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AccessToken));
|
|
|
|
// Note: only the left-most half of the hash is used.
|
|
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
|
context.IdentityTokenPrincipal.SetClaim(Claims.AccessTokenHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(context.AuthorizationCode))
|
|
{
|
|
var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AuthorizationCode));
|
|
|
|
// Note: only the left-most half of the hash is used.
|
|
// See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
|
context.IdentityTokenPrincipal.SetClaim(Claims.CodeHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2));
|
|
}
|
|
|
|
return default;
|
|
|
|
static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials)
|
|
{
|
|
HashAlgorithm? hash = null;
|
|
|
|
if (!string.IsNullOrEmpty(credentials.Digest))
|
|
{
|
|
hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm;
|
|
}
|
|
|
|
if (hash is null)
|
|
{
|
|
var algorithm = credentials.Digest switch
|
|
{
|
|
SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256,
|
|
SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384,
|
|
SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512,
|
|
|
|
_ => credentials.Algorithm switch
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature
|
|
=> HashAlgorithmName.SHA256,
|
|
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature
|
|
=> HashAlgorithmName.SHA384,
|
|
SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature
|
|
=> HashAlgorithmName.SHA512,
|
|
#endif
|
|
SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature
|
|
=> HashAlgorithmName.SHA256,
|
|
SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature
|
|
=> HashAlgorithmName.SHA384,
|
|
SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature
|
|
=> HashAlgorithmName.SHA512,
|
|
|
|
SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature
|
|
=> HashAlgorithmName.SHA256,
|
|
SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature
|
|
=> HashAlgorithmName.SHA384,
|
|
SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature
|
|
=> HashAlgorithmName.SHA512,
|
|
|
|
SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature
|
|
=> HashAlgorithmName.SHA256,
|
|
SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature
|
|
=> HashAlgorithmName.SHA384,
|
|
SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature
|
|
=> HashAlgorithmName.SHA512,
|
|
|
|
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267))
|
|
}
|
|
};
|
|
|
|
hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm;
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating a user code for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateUserCode : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateUserCode(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireUserCodeGenerated>()
|
|
.UseScopedHandler<GenerateUserCode>()
|
|
.SetOrder(AttachTokenDigests.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
PersistTokenPayload = !context.Options.DisableTokenStorage,
|
|
Principal = context.UserCodePrincipal!,
|
|
TokenType = TokenTypeHints.UserCode
|
|
};
|
|
|
|
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.UserCode = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of generating an identity token for the current sign-in operation.
|
|
/// </summary>
|
|
public class GenerateIdentityToken : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public GenerateIdentityToken(IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.AddFilter<RequireIdentityTokenGenerated>()
|
|
.UseScopedHandler<GenerateIdentityToken>()
|
|
.SetOrder(GenerateUserCode.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public async ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var notification = new GenerateTokenContext(context.Transaction)
|
|
{
|
|
ClientId = context.ClientId,
|
|
CreateTokenEntry = !context.Options.DisableTokenStorage,
|
|
// Identity tokens cannot never be reference tokens.
|
|
PersistTokenPayload = false,
|
|
Principal = context.IdentityTokenPrincipal!,
|
|
TokenType = TokenTypeHints.IdToken
|
|
};
|
|
|
|
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.IdentityToken = notification.Token;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the appropriate parameters to the sign-in response.
|
|
/// </summary>
|
|
public class AttachSignInParameters : IOpenIddictServerHandler<ProcessSignInContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
|
|
.UseSingletonHandler<AttachSignInParameters>()
|
|
.SetOrder(GenerateIdentityToken.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignInContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.IncludeAccessToken)
|
|
{
|
|
context.Response.AccessToken = context.AccessToken;
|
|
context.Response.TokenType = TokenTypes.Bearer;
|
|
|
|
// If the principal is available, attach additional metadata.
|
|
if (context.AccessTokenPrincipal is not null)
|
|
{
|
|
// If an expiration date was set on the access token principal, return it to the client application.
|
|
var date = context.AccessTokenPrincipal.GetExpirationDate();
|
|
if (date.HasValue && date.Value > DateTimeOffset.UtcNow)
|
|
{
|
|
context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5);
|
|
}
|
|
|
|
// If the granted access token scopes differ from the requested scopes, return the granted scopes
|
|
// list as a parameter to inform the client application of the fact the scopes set will be reduced.
|
|
var scopes = new HashSet<string>(context.AccessTokenPrincipal.GetScopes(), StringComparer.Ordinal);
|
|
if ((context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsAuthorizationCodeGrantType()) ||
|
|
!scopes.SetEquals(context.Request.GetScopes()))
|
|
{
|
|
context.Response.Scope = string.Join(" ", scopes);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (context.IncludeAuthorizationCode)
|
|
{
|
|
context.Response.Code = context.AuthorizationCode;
|
|
}
|
|
|
|
if (context.IncludeDeviceCode)
|
|
{
|
|
context.Response.DeviceCode = context.DeviceCode;
|
|
|
|
// If the principal is available, attach additional metadata.
|
|
if (context.DeviceCodePrincipal is not null)
|
|
{
|
|
// If an expiration date was set on the device code principal, return it to the client application.
|
|
var date = context.DeviceCodePrincipal.GetExpirationDate();
|
|
if (date.HasValue && date.Value > DateTimeOffset.UtcNow)
|
|
{
|
|
context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (context.IncludeIdentityToken)
|
|
{
|
|
context.Response.IdToken = context.IdentityToken;
|
|
}
|
|
|
|
if (context.IncludeRefreshToken)
|
|
{
|
|
context.Response.RefreshToken = context.RefreshToken;
|
|
}
|
|
|
|
if (context.IncludeUserCode)
|
|
{
|
|
context.Response.UserCode = context.UserCode;
|
|
|
|
var address = GetEndpointAbsoluteUri(context.Issuer, context.Options.VerificationEndpointUris.FirstOrDefault());
|
|
if (address is not null)
|
|
{
|
|
var builder = new UriBuilder(address)
|
|
{
|
|
Query = string.Concat(Parameters.UserCode, "=", context.UserCode)
|
|
};
|
|
|
|
context.Response[Parameters.VerificationUri] = address.AbsoluteUri;
|
|
context.Response[Parameters.VerificationUriComplete] = builder.Uri.AbsoluteUri;
|
|
}
|
|
}
|
|
|
|
if (context.Parameters.Count > 0)
|
|
{
|
|
foreach (var parameter in context.Parameters)
|
|
{
|
|
context.Response.SetParameter(parameter.Key, parameter.Value);
|
|
}
|
|
}
|
|
|
|
return default;
|
|
|
|
static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint)
|
|
{
|
|
// If the endpoint is disabled (i.e a null address is specified), return null.
|
|
if (endpoint is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// If the endpoint address is already an absolute URL, return it as-is.
|
|
if (endpoint.IsAbsoluteUri)
|
|
{
|
|
return endpoint;
|
|
}
|
|
|
|
// At this stage, throw an exception if the issuer cannot be retrieved.
|
|
if (issuer is null || !issuer.IsAbsoluteUri)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0023));
|
|
}
|
|
|
|
// Ensure the issuer ends with a trailing slash, as it is necessary
|
|
// for Uri's constructor to correctly compute correct absolute URLs.
|
|
if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal))
|
|
{
|
|
issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute);
|
|
}
|
|
|
|
// Ensure the endpoint does not start with a leading slash, as it is necessary
|
|
// for Uri's constructor to correctly compute correct absolute URLs.
|
|
if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal))
|
|
{
|
|
endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative);
|
|
}
|
|
|
|
return new Uri(issuer, endpoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of ensuring that the sign-out demand
|
|
/// is compatible with the type of the endpoint that handled the request.
|
|
/// </summary>
|
|
public class ValidateSignOutDemand : IOpenIddictServerHandler<ProcessSignOutContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
|
|
.UseSingletonHandler<ValidateSignOutDemand>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignOutContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Logout)
|
|
{
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0024));
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the appropriate parameters to the sign-out response.
|
|
/// </summary>
|
|
public class AttachSignOutParameters : IOpenIddictServerHandler<ProcessSignOutContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
|
|
.UseSingletonHandler<AttachSignOutParameters>()
|
|
.SetOrder(ValidateSignOutDemand.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HandleAsync(ProcessSignOutContext context)
|
|
{
|
|
if (context is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.Parameters.Count > 0)
|
|
{
|
|
foreach (var parameter in context.Parameters)
|
|
{
|
|
context.Response.SetParameter(parameter.Key, parameter.Value);
|
|
}
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|