From 9262d5aa3e1a086e41a91474dba1803c5dda974f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sun, 22 Sep 2019 18:08:17 +0200 Subject: [PATCH] Bring back automatic authorization validation and ad-hoc authorization creation --- .../OpenIddictAuthorizationDescriptor.cs | 5 +- .../IOpenIddictAuthorizationManager.cs | 5 +- .../OpenIddictConstants.cs | 2 + .../Primitives/OpenIddictExtensions.cs | 70 +++++++++- .../OpenIddictAuthorizationManager.cs | 23 ++-- ...OpenIddictServerDataProtectionConstants.cs | 2 + ...verDataProtectionHandlers.Serialization.cs | 13 ++ .../OpenIddictServerExtensions.cs | 1 + .../OpenIddictServerHandlerFilters.cs | 16 +++ .../OpenIddictServerHandlers.Exchange.cs | 62 +++++++++ .../OpenIddictServerHandlers.Introspection.cs | 62 ++++++++- .../OpenIddictServerHandlers.Serialization.cs | 2 + .../OpenIddictServerHandlers.cs | 122 +++++++++++++++++- 13 files changed, 357 insertions(+), 28 deletions(-) diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs index 44cc3353..4dbf2ce1 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Claims; namespace OpenIddict.Abstractions { @@ -14,10 +15,10 @@ namespace OpenIddict.Abstractions public string ApplicationId { get; set; } /// - /// Gets the claims associated with the authorization. + /// Gets or sets the optional principal associated with the authorization. /// Note: this property is not stored by the default authorization stores. /// - public IDictionary Claims { get; } = new Dictionary(StringComparer.Ordinal); + public ClaimsPrincipal Principal { get; set; } /// /// Gets the scopes associated with the authorization. diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs index 4ffa9861..6cbb1925 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -45,7 +46,7 @@ namespace OpenIddict.Abstractions /// /// Creates a new permanent authorization based on the specified parameters. /// - /// The claims associated with the authorization. + /// The principal associated with the authorization. /// The subject associated with the authorization. /// The client associated with the authorization. /// The authorization type. @@ -55,7 +56,7 @@ namespace OpenIddict.Abstractions /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. /// ValueTask CreateAsync( - [NotNull] ImmutableDictionary claims, [NotNull] string subject, [NotNull] string client, + [NotNull] ClaimsPrincipal principal, [NotNull] string subject, [NotNull] string client, [NotNull] string type, ImmutableArray scopes, CancellationToken cancellationToken = default); /// diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs index b64994f9..36f48b59 100644 --- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs +++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs @@ -88,6 +88,7 @@ namespace OpenIddict.Abstractions public static class Private { public const string AccessTokenLifetime = "oi_act_lft"; + public const string AuthorizationId = "oi_au_id"; public const string AuthorizationCodeLifetime = "oi_auc_lft"; public const string ClaimDestinations = "oi_cl_dstn"; public const string CodeChallenge = "oi_cd_chlg"; @@ -97,6 +98,7 @@ namespace OpenIddict.Abstractions public const string RedirectUri = "oi_reduri"; public const string RefreshTokenLifetime = "oi_reft_lft"; public const string Resource = "oi_rsrc"; + public const string TokenId = "oi_tkn_id"; public const string TokenUsage = "oi_tkn_use"; } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index f53c040d..fa21789e 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs @@ -1246,11 +1246,41 @@ namespace OpenIddict.Abstractions } /// - /// Gets the unique identifier associated with the claims principal. + /// Gets the internal authorization identifier associated with the claims principal. /// /// The claims principal. /// The unique identifier or null if the claim cannot be found. - public static string GetTokenId([NotNull] this ClaimsPrincipal principal) + public static string GetInternalAuthorizationId([NotNull] this ClaimsPrincipal principal) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + return principal.GetClaim(Claims.Private.AuthorizationId); + } + + /// + /// Gets the internal token identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier or null if the claim cannot be found. + public static string GetInternalTokenId([NotNull] this ClaimsPrincipal principal) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + return principal.GetClaim(Claims.Private.TokenId); + } + + /// + /// Gets the public token identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier or null if the claim cannot be found. + public static string GetPublicTokenId([NotNull] this ClaimsPrincipal principal) { if (principal == null) { @@ -1731,12 +1761,44 @@ namespace OpenIddict.Abstractions } /// - /// Sets the unique identifier associated with the claims principal. + /// Sets the internal authorization identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier to store. + /// The claims principal. + public static ClaimsPrincipal SetInternalAuthorizationId([NotNull] this ClaimsPrincipal principal, string identifier) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + return principal.SetClaim(Claims.Private.AuthorizationId, identifier); + } + + /// + /// Sets the internal token identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier to store. + /// The claims principal. + public static ClaimsPrincipal SetInternalTokenId([NotNull] this ClaimsPrincipal principal, string identifier) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + return principal.SetClaim(Claims.Private.TokenId, identifier); + } + + /// + /// Sets the public token identifier associated with the claims principal. /// /// The claims principal. /// The unique identifier to store. /// The claims principal. - public static ClaimsPrincipal SetTokenId([NotNull] this ClaimsPrincipal principal, string identifier) + public static ClaimsPrincipal SetPublicTokenId([NotNull] this ClaimsPrincipal principal, string identifier) { if (principal == null) { diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index f20ef182..073d639e 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.CompilerServices; +using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -165,7 +166,7 @@ namespace OpenIddict.Core /// /// Creates a new permanent authorization based on the specified parameters. /// - /// The claims associated with the authorization. + /// The principal associated with the authorization. /// The subject associated with the authorization. /// The client associated with the authorization. /// The authorization type. @@ -175,12 +176,12 @@ namespace OpenIddict.Core /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. /// public virtual ValueTask CreateAsync( - [NotNull] ImmutableDictionary claims, [NotNull] string subject, - [NotNull] string client, [NotNull] string type, ImmutableArray scopes, CancellationToken cancellationToken = default) + [NotNull] ClaimsPrincipal principal, [NotNull] string subject, [NotNull] string client, + [NotNull] string type, ImmutableArray scopes, CancellationToken cancellationToken = default) { - if (claims == null) + if (principal == null) { - throw new ArgumentNullException(nameof(claims)); + throw new ArgumentNullException(nameof(principal)); } if (string.IsNullOrEmpty(subject)) @@ -201,6 +202,7 @@ namespace OpenIddict.Core var descriptor = new OpenIddictAuthorizationDescriptor { ApplicationId = client, + Principal = principal, Status = OpenIddictConstants.Statuses.Valid, Subject = subject, Type = type @@ -208,11 +210,6 @@ namespace OpenIddict.Core descriptor.Scopes.UnionWith(scopes); - foreach (var claim in claims) - { - descriptor.Claims.Add(claim); - } - return CreateAsync(descriptor, cancellationToken); } @@ -1035,8 +1032,6 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(authorization)); } - var builder = ImmutableArray.CreateBuilder(); - var type = await Store.GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { @@ -1084,8 +1079,8 @@ namespace OpenIddict.Core ValueTask IOpenIddictAuthorizationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) => CountAsync(query, cancellationToken); - async ValueTask IOpenIddictAuthorizationManager.CreateAsync(ImmutableDictionary claims, string subject, string client, string type, ImmutableArray scopes, CancellationToken cancellationToken) - => await CreateAsync(claims, subject, client, type, scopes, cancellationToken); + async ValueTask IOpenIddictAuthorizationManager.CreateAsync(ClaimsPrincipal principal, string subject, string client, string type, ImmutableArray scopes, CancellationToken cancellationToken) + => await CreateAsync(principal, subject, client, type, scopes, cancellationToken); async ValueTask IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) => await CreateAsync(descriptor, cancellationToken); diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs index 0713e1ed..3b606019 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs @@ -18,6 +18,8 @@ namespace OpenIddict.Server.DataProtection public const string DataProtector = ".data_protector"; public const string Expires = ".expires"; public const string IdentityTokenLifetime = ".identity_token_lifetime"; + public const string InternalAuthorizationId = ".internal_authorization_id"; + public const string InternalTokenId = ".internal_token_id"; public const string Issued = ".issued"; public const string Nonce = ".nonce"; public const string OriginalRedirectUri = ".original_redirect_uri"; diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs index a8963fd2..05866246 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs @@ -116,20 +116,31 @@ namespace OpenIddict.Server.DataProtection SetProperty(properties, Properties.AccessTokenLifetime, context.Principal.GetClaim(Claims.Private.AccessTokenLifetime)); + SetProperty(properties, Properties.AuthorizationCodeLifetime, context.Principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + SetProperty(properties, Properties.CodeChallenge, context.Principal.GetClaim(Claims.Private.CodeChallenge)); + SetProperty(properties, Properties.CodeChallengeMethod, context.Principal.GetClaim(Claims.Private.CodeChallengeMethod)); + SetProperty(properties, Properties.Expires, context.Principal.GetExpirationDate()?.ToString("r", CultureInfo.InvariantCulture)); + SetProperty(properties, Properties.IdentityTokenLifetime, context.Principal.GetClaim(Claims.Private.IdentityTokenLifetime)); + + SetProperty(properties, Properties.InternalAuthorizationId, context.Principal.GetInternalAuthorizationId()); + SetProperty(properties, Properties.InternalTokenId, context.Principal.GetInternalTokenId()); + SetProperty(properties, Properties.Issued, context.Principal.GetCreationDate()?.ToString("r", CultureInfo.InvariantCulture)); + SetProperty(properties, Properties.OriginalRedirectUri, context.Principal.GetClaim(Claims.Private.RedirectUri)); + SetProperty(properties, Properties.RefreshTokenLifetime, context.Principal.GetClaim(Claims.Private.RefreshTokenLifetime)); @@ -342,11 +353,13 @@ namespace OpenIddict.Server.DataProtection .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) + .SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) .SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) .SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) .SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) .SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) .SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) + .SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)) // Note: since the data format relies on a data protector using different "purposes" strings // per token type, the token processed at this stage is guaranteed to be of the expected type. diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs index ce56f5ad..62ebed15 100644 --- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -45,6 +45,7 @@ namespace Microsoft.Extensions.DependencyInjection // Register the built-in filters used by the default OpenIddict server event handlers. builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs index fcb5924d..a45f7491 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs @@ -47,6 +47,22 @@ namespace OpenIddict.Server } } + /// + /// Represents a filter that excludes the associated handlers if authorization storage was not enabled. + /// + public class RequireAuthorizationStorageEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync([NotNull] BaseContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new ValueTask(!context.Options.DisableAuthorizationStorage); + } + } + /// /// Represents a filter that excludes the associated handlers when no client identifier is received. /// diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 402bf6a1..9b602216 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -60,6 +60,7 @@ namespace OpenIddict.Server ValidateRedirectUri.Descriptor, ValidateCodeVerifier.Descriptor, ValidateGrantedScopes.Descriptor, + ValidateAuthorization.Descriptor, /* * Token request handling: @@ -1687,6 +1688,67 @@ namespace OpenIddict.Server } } + /// + /// Contains the logic responsible of rejecting token requests that use an authorization code + /// or refresh token whose associated authorization is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateAuthorization : IOpenIddictServerHandler + { + private readonly IOpenIddictAuthorizationManager _authorizationManager; + + public ValidateAuthorization() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public ValidateAuthorization([NotNull] IOpenIddictAuthorizationManager authorizationManager) + => _authorizationManager = authorizationManager; + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateGrantedScopes.Descriptor.Order + 1_000) + .Build(); + + public async ValueTask HandleAsync([NotNull] ValidateTokenRequestContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var identifier = context.Principal.GetInternalAuthorizationId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } + + var authorization = await _authorizationManager.FindByIdAsync(identifier); + if (authorization == null || !await _authorizationManager.IsValidAsync(authorization)) + { + context.Logger.LogError("The token '{Identifier}' was rejected because the associated " + + "authorization was no longer valid.", context.Principal.GetPublicTokenId()); + + context.Reject( + error: Errors.InvalidGrant, + description: context.Request.IsAuthorizationCodeGrantType() ? + "The authorization associated with the authorization code is no longer valid." : + "The authorization associated with the refresh token is no longer valid."); + + return; + } + } + } + /// /// Contains the logic responsible of attaching the principal extracted /// from the authorization code/refresh token to the event context. diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index c2dd1196..79d7df38 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs @@ -48,6 +48,7 @@ namespace OpenIddict.Server ValidateEndpointPermissions.Descriptor, ValidateToken.Descriptor, ValidateAuthorizedParty.Descriptor, + ValidateAuthorization.Descriptor, /* * Introspection request handling: @@ -918,6 +919,65 @@ namespace OpenIddict.Server } } + /// + /// Contains the logic responsible of rejecting introspection requests that use + /// a token whose associated authorization is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateAuthorization : IOpenIddictServerHandler + { + private readonly IOpenIddictAuthorizationManager _authorizationManager; + + public ValidateAuthorization() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public ValidateAuthorization([NotNull] IOpenIddictAuthorizationManager authorizationManager) + => _authorizationManager = authorizationManager; + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateAuthorizedParty.Descriptor.Order + 1_000) + .Build(); + + public async ValueTask HandleAsync([NotNull] ValidateIntrospectionRequestContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var identifier = context.Principal.GetInternalAuthorizationId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } + + var authorization = await _authorizationManager.FindByIdAsync(identifier); + if (authorization == null || !await _authorizationManager.IsValidAsync(authorization)) + { + context.Logger.LogError("The token '{Identifier}' was rejected because the associated " + + "authorization was no longer valid.", context.Principal.GetPublicTokenId()); + + context.Reject( + error: Errors.InvalidGrant, + description: "The authorization associated with the token is no longer valid."); + + return; + } + } + } + /// /// Contains the logic responsible of attaching the principal /// extracted from the introspected token to the event context. @@ -984,7 +1044,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - context.TokenId = context.Principal.GetTokenId(); + context.TokenId = context.Principal.GetPublicTokenId(); context.TokenUsage = context.Principal.GetTokenUsage(); context.Subject = context.Principal.GetClaim(Claims.Subject); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs index 4977ee76..b27aff1c 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs @@ -208,6 +208,8 @@ namespace OpenIddict.Server { context.Logger.LogTrace("The token '{Token}' could not be validated.", context.Token); } + + return default; } var assertion = ((JsonWebToken) result.SecurityToken)?.InnerToken ?? (JsonWebToken) result.SecurityToken; diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index fabd75b0..8b1bcade 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -45,6 +45,7 @@ namespace OpenIddict.Server AttachDefaultPresenters.Descriptor, InferResources.Descriptor, EvaluateReturnedTokens.Descriptor, + AttachAuthorization.Descriptor, AttachAccessToken.Descriptor, AttachAuthorizationCode.Descriptor, AttachRefreshToken.Descriptor, @@ -428,6 +429,117 @@ namespace OpenIddict.Server } } + /// + /// Contains the logic responsible of creating an ad-hoc authorization, if necessary. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class AttachAuthorization : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictAuthorizationManager _authorizationManager; + + public AttachAuthorization() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public AttachAuthorization( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictAuthorizationManager authorizationManager) + { + _applicationManager = applicationManager; + _authorizationManager = authorizationManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(EvaluateReturnedTokens.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSigninContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // If no authorization code or refresh token is returned, don't create an authorization. + if (!context.IncludeAuthorizationCode && !context.IncludeRefreshToken) + { + return; + } + + // If an authorization identifier was explicitly specified, don't create an ad-hoc authorization. + if (!string.IsNullOrEmpty(context.Principal.GetInternalAuthorizationId())) + { + return; + } + + var descriptor = new OpenIddictAuthorizationDescriptor + { + 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 == null) + { + throw new InvalidOperationException("The application entry cannot be found in the database."); + } + + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } + + var authorization = await _authorizationManager.CreateAsync(descriptor); + if (authorization == null) + { + return; + } + + var identifier = await _authorizationManager.GetIdAsync(authorization); + + if (string.IsNullOrEmpty(context.Request.ClientId)) + { + context.Logger.LogInformation("An ad hoc authorization was automatically created and " + + "associated with an unknown application: {Identifier}.", identifier); + } + + else + { + context.Logger.LogInformation("An ad hoc authorization was automatically created and " + + "associated with the '{ClientId}' application: {Identifier}.", + 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.SetInternalAuthorizationId(identifier); + } + } + /// /// Contains the logic responsible of generating and attaching an access token. /// @@ -445,7 +557,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() - .SetOrder(EvaluateReturnedTokens.Descriptor.Order + 1_000) + .SetOrder(AttachAuthorization.Descriptor.Order + 1_000) .Build(); /// @@ -496,7 +608,7 @@ namespace OpenIddict.Server claim.Properties.Remove(OpenIddictConstants.Properties.Destinations); } - principal.SetTokenId(Guid.NewGuid().ToString()).SetCreationDate(DateTimeOffset.UtcNow); + principal.SetPublicTokenId(Guid.NewGuid().ToString()).SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime; if (lifetime.HasValue) @@ -583,7 +695,7 @@ namespace OpenIddict.Server } var principal = context.Principal.Clone(_ => true) - .SetTokenId(Guid.NewGuid().ToString()) + .SetPublicTokenId(Guid.NewGuid().ToString()) .SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime; @@ -664,7 +776,7 @@ namespace OpenIddict.Server } var principal = context.Principal.Clone(_ => true) - .SetTokenId(Guid.NewGuid().ToString()) + .SetPublicTokenId(Guid.NewGuid().ToString()) .SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; @@ -753,7 +865,7 @@ namespace OpenIddict.Server claim.Properties.Remove(OpenIddictConstants.Properties.Destinations); } - principal.SetTokenId(Guid.NewGuid().ToString()).SetCreationDate(DateTimeOffset.UtcNow); + principal.SetPublicTokenId(Guid.NewGuid().ToString()).SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime; if (lifetime.HasValue)