diff --git a/build/dependencies.props b/build/dependencies.props index 4c610785..33958ca5 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -6,6 +6,7 @@ 2.0.0 4.4.0 3.0.1.1 + 4.4.0 6.1.3 10.3.0 10.0.2 diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index af9ec407..eefa5676 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -133,19 +134,13 @@ namespace OpenIddict.Core await Store.SetClientSecretAsync(application, secret, cancellationToken); } - await ValidateAsync(application, cancellationToken); - - try + var results = await ValidateAsync(application, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.CreateAsync(application, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to create a new application."); - - throw; - } + await Store.CreateAsync(application, cancellationToken); } /// @@ -197,24 +192,14 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) + public virtual Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - try - { - await Store.DeleteAsync(application, cancellationToken); - } - - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to delete an existing application."); - - throw; - } + return Store.DeleteAsync(application, cancellationToken); } /// @@ -639,19 +624,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(application)); } - await ValidateAsync(application, cancellationToken); - - try + var results = await ValidateAsync(application, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.UpdateAsync(application, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to update an existing application."); - - throw; - } + await Store.UpdateAsync(application, cancellationToken); } /// @@ -684,7 +663,12 @@ namespace OpenIddict.Core await Store.SetClientSecretAsync(application, secret, cancellationToken); } - await ValidateAsync(application, cancellationToken); + var results = await ValidateAsync(application, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application); + } + await UpdateAsync(application, cancellationToken); } @@ -767,6 +751,110 @@ namespace OpenIddict.Core await UpdateAsync(application, cancellationToken); } + /// + /// Validates the application to ensure it's in a consistent state. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the validation error encountered when validating the application. + /// + public virtual async Task> ValidateAsync( + [NotNull] TApplication application, CancellationToken cancellationToken = default) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + var results = ImmutableArray.CreateBuilder(); + + var identifier = await Store.GetClientIdAsync(application, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + results.Add(new ValidationResult("The client identifier cannot be null or empty.")); + } + + else + { + // Ensure the client_id is not already used for a different application. + var other = await Store.FindByClientIdAsync(identifier, cancellationToken); + if (other != null && !string.Equals( + await Store.GetIdAsync(other, cancellationToken), + await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal)) + { + results.Add(new ValidationResult("An application with the same client identifier already exists.")); + } + } + + var type = await Store.GetClientTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + results.Add(new ValidationResult("The client type cannot be null or empty.")); + } + + else + { + // Ensure the application type is supported by the manager. + if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, OpenIddictConstants.ClientTypes.Hybrid, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + { + results.Add(new ValidationResult("Only 'confidential', 'hybrid' or 'public' applications are " + + "supported by the default application manager.")); + } + + // Ensure a client secret was specified if the client is a confidential application. + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + if (string.IsNullOrEmpty(secret) && + string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) + { + results.Add(new ValidationResult("The client secret cannot be null or empty for a confidential application.")); + } + + // Ensure no client secret was specified if the client is a public application. + else if (!string.IsNullOrEmpty(secret) && + string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + { + results.Add(new ValidationResult("A client secret cannot be associated with a public application.")); + } + } + + // When callback URLs are specified, ensure they are valid and spec-compliant. + // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information. + foreach (var address in ImmutableArray.Create() + .AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) + .AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) + { + // Ensure the address is not null or empty. + if (string.IsNullOrEmpty(address)) + { + results.Add(new ValidationResult("Callback URLs cannot be null or empty.")); + + break; + } + + // Ensure the address is a valid absolute URL. + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString()) + { + results.Add(new ValidationResult("Callback URLs must be valid absolute URLs.")); + + break; + } + + // Ensure the address doesn't contain a fragment. + if (!string.IsNullOrEmpty(uri.Fragment)) + { + results.Add(new ValidationResult("Callback URLs cannot contain a fragment.")); + + break; + } + } + + return results.ToImmutable(); + } + /// /// Validates the client_secret associated with an application. /// @@ -928,87 +1016,6 @@ namespace OpenIddict.Core descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken); } - /// - /// Validates the application to ensure it's in a consistent state. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) - { - var identifier = await Store.GetClientIdAsync(application, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException("The client identifier cannot be null or empty.", nameof(application)); - } - - // Ensure the client_id is not already used for a different application. - var other = await Store.FindByClientIdAsync(identifier, cancellationToken); - if (other != null && !string.Equals( - await Store.GetIdAsync(other, cancellationToken), - await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal)) - { - throw new ArgumentException("An application with the same client identifier already exists.", nameof(application)); - } - - var type = await Store.GetClientTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException("The client type cannot be null or empty.", nameof(application)); - } - - // Ensure the application type is supported by the manager. - if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, OpenIddictConstants.ClientTypes.Hybrid, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Only 'confidential', 'hybrid' or 'public' applications are " + - "supported by the default application manager.", nameof(application)); - } - - // Ensure a client secret was specified if the client is a confidential application. - var secret = await Store.GetClientSecretAsync(application, cancellationToken); - if (string.IsNullOrEmpty(secret) && - string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(application)); - } - - // Ensure no client secret was specified if the client is a public application. - else if (!string.IsNullOrEmpty(secret) && - string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(application)); - } - - // When callback URLs are specified, ensure they are valid and spec-compliant. - // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information. - foreach (var address in ImmutableArray.Create() - .AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) - .AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) - { - // Ensure the address is not null or empty. - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException("Callback URLs cannot be null or empty."); - } - - // Ensure the address is a valid absolute URL. - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString()) - { - throw new ArgumentException("Callback URLs must be valid absolute URLs."); - } - - // Ensure the address doesn't contain a fragment. - if (!string.IsNullOrEmpty(uri.Fragment)) - { - throw new ArgumentException("Callback URLs cannot contain a fragment."); - } - } - } - /// /// Obfuscates the specified client secret so it can be safely stored in a database. /// By default, this method returns a complex hashed representation computed using PBKDF2. diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 340a38da..18be7633 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -93,19 +94,13 @@ namespace OpenIddict.Core await Store.SetTypeAsync(authorization, OpenIddictConstants.AuthorizationTypes.Permanent, cancellationToken); } - await ValidateAsync(authorization, cancellationToken); - - try + var results = await ValidateAsync(authorization, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.CreateAsync(authorization, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to create a new authorization."); - - throw; - } + await Store.CreateAsync(authorization, cancellationToken); } /// @@ -144,24 +139,14 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) + public virtual Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } - try - { - await Store.DeleteAsync(authorization, cancellationToken); - } - - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to delete an existing authorization."); - - throw; - } + return Store.DeleteAsync(authorization, cancellationToken); } /// @@ -563,7 +548,13 @@ namespace OpenIddict.Core if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase)) { await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken); - await ValidateAsync(authorization, cancellationToken); + + var results = await ValidateAsync(authorization, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); + } + await UpdateAsync(authorization, cancellationToken); } } @@ -604,19 +595,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(authorization)); } - await ValidateAsync(authorization, cancellationToken); - - try + var results = await ValidateAsync(authorization, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.UpdateAsync(authorization, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to update an existing authorization."); - - throw; - } + await Store.UpdateAsync(authorization, cancellationToken); } /// @@ -654,70 +639,45 @@ namespace OpenIddict.Core await UpdateAsync(authorization, cancellationToken); } - /// - /// Populates the authorization using the specified descriptor. - /// - /// The authorization. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task PopulateAsync([NotNull] TAuthorization authorization, - [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) - { - if (authorization == null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken); - await Store.SetScopesAsync(authorization, ImmutableArray.CreateRange(descriptor.Scopes), cancellationToken); - await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken); - await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken); - await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken); - } - /// /// Validates the authorization to ensure it's in a consistent state. /// /// The authorization. /// The that can be used to abort the operation. /// - /// A that can be used to monitor the asynchronous operation. + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the validation error encountered when validating the authorization. /// - protected virtual async Task ValidateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) + public virtual async Task> ValidateAsync( + [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } + var results = ImmutableArray.CreateBuilder(); + var type = await Store.GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The authorization type cannot be null or empty.", nameof(authorization)); + results.Add(new ValidationResult("The authorization type cannot be null or empty.")); } - if (!string.Equals(type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException("The specified authorization type is not supported by the default token manager.", nameof(authorization)); + results.Add(new ValidationResult("The specified authorization type is not supported by the default token manager.")); } if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) { - throw new ArgumentException("The status cannot be null or empty.", nameof(authorization)); + results.Add(new ValidationResult("The status cannot be null or empty.")); } if (string.IsNullOrEmpty(await Store.GetSubjectAsync(authorization, cancellationToken))) { - throw new ArgumentException("The subject cannot be null or empty.", nameof(authorization)); + results.Add(new ValidationResult("The subject cannot be null or empty.")); } // Ensure that the scopes are not null or empty and do not contain spaces. @@ -725,14 +685,49 @@ namespace OpenIddict.Core { if (string.IsNullOrEmpty(scope)) { - throw new ArgumentException("Scopes cannot be null or empty.", nameof(authorization)); + results.Add(new ValidationResult("Scopes cannot be null or empty.")); + + break; } if (scope.Contains(OpenIddictConstants.Separators.Space)) { - throw new ArgumentException("Scopes cannot contain spaces.", nameof(authorization)); + results.Add(new ValidationResult("Scopes cannot contain spaces.")); + + break; } } + + return results.ToImmutable(); + } + + /// + /// Populates the authorization using the specified descriptor. + /// + /// The authorization. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + protected virtual async Task PopulateAsync([NotNull] TAuthorization authorization, + [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken); + await Store.SetScopesAsync(authorization, ImmutableArray.CreateRange(descriptor.Scopes), cancellationToken); + await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken); + await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken); + await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index 1319f759..6796b4e8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -87,17 +88,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(scope)); } - try + var results = await ValidateAsync(scope, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.CreateAsync(scope, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, scope); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to create a new scope."); - - throw; - } + await Store.CreateAsync(scope, cancellationToken); } /// @@ -136,24 +133,14 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken = default) + public virtual Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken = default) { if (scope == null) { throw new ArgumentNullException(nameof(scope)); } - try - { - await Store.DeleteAsync(scope, cancellationToken); - } - - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to delete an existing scope."); - - throw; - } + return Store.DeleteAsync(scope, cancellationToken); } /// @@ -305,17 +292,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(scope)); } - try + var results = await ValidateAsync(scope, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.UpdateAsync(scope, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, scope); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to update an existing scope."); - - throw; - } + await Store.UpdateAsync(scope, cancellationToken); } /// @@ -346,6 +329,33 @@ namespace OpenIddict.Core await UpdateAsync(scope, cancellationToken); } + /// + /// Validates the scope to ensure it's in a consistent state. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the validation error encountered when validating the scope. + /// + public virtual async Task> ValidateAsync( + [NotNull] TScope scope, CancellationToken cancellationToken = default) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + var results = ImmutableArray.CreateBuilder(); + + if (string.IsNullOrEmpty(await Store.GetNameAsync(scope, cancellationToken))) + { + results.Add(new ValidationResult("The scope name cannot be null or empty.")); + } + + return results.ToImmutable(); + } + /// /// Populates the scope using the specified descriptor. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 7f75f002..36b3ae80 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -89,19 +90,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(token)); } - await ValidateAsync(token, cancellationToken); - - try + var results = await ValidateAsync(token, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.CreateAsync(token, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, token); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to create a new token."); - - throw; - } + await Store.CreateAsync(token, cancellationToken); } /// @@ -140,24 +135,14 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken = default) + public virtual Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken = default) { if (token == null) { throw new ArgumentNullException(nameof(token)); } - try - { - await Store.DeleteAsync(token, cancellationToken); - } - - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to delete an existing token."); - - throw; - } + return Store.DeleteAsync(token, cancellationToken); } /// @@ -737,17 +722,13 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(token)); } - try + var results = await ValidateAsync(token, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - await Store.UpdateAsync(token, cancellationToken); + throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, token); } - catch (Exception exception) - { - Logger.LogError(exception, "An exception occurred while trying to update an existing token."); - - throw; - } + await Store.UpdateAsync(token, cancellationToken); } /// @@ -785,54 +766,25 @@ namespace OpenIddict.Core await UpdateAsync(token, cancellationToken); } - /// - /// Populates the token using the specified descriptor. - /// - /// The token. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task PopulateAsync([NotNull] TToken token, - [NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) - { - if (token == null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - await Store.SetApplicationIdAsync(token, descriptor.ApplicationId, cancellationToken); - await Store.SetAuthorizationIdAsync(token, descriptor.AuthorizationId, cancellationToken); - await Store.SetCreationDateAsync(token, descriptor.CreationDate, cancellationToken); - await Store.SetExpirationDateAsync(token, descriptor.ExpirationDate, cancellationToken); - await Store.SetPayloadAsync(token, descriptor.Payload, cancellationToken); - await Store.SetReferenceIdAsync(token, descriptor.ReferenceId, cancellationToken); - await Store.SetStatusAsync(token, descriptor.Status, cancellationToken); - await Store.SetSubjectAsync(token, descriptor.Subject, cancellationToken); - await Store.SetTokenTypeAsync(token, descriptor.Type, cancellationToken); - } - /// /// Validates the token to ensure it's in a consistent state. /// /// The token. /// The that can be used to abort the operation. /// - /// A that can be used to monitor the asynchronous operation. + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the validation error encountered when validating the token. /// - protected virtual async Task ValidateAsync([NotNull] TToken token, CancellationToken cancellationToken = default) + public virtual async Task> ValidateAsync( + [NotNull] TToken token, CancellationToken cancellationToken = default) { if (token == null) { throw new ArgumentNullException(nameof(token)); } + var results = ImmutableArray.CreateBuilder(); + // If a reference identifier was associated with the token, // ensure it's not already used for a different token. var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); @@ -843,32 +795,67 @@ namespace OpenIddict.Core await Store.GetIdAsync(other, cancellationToken), await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal)) { - throw new ArgumentException("A token with the same reference identifier already exists.", nameof(token)); + results.Add(new ValidationResult("A token with the same reference identifier already exists.")); } } var type = await Store.GetTokenTypeAsync(token, cancellationToken); if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty.", nameof(token)); + results.Add(new ValidationResult("The token type cannot be null or empty.")); } - if (!string.Equals(type, OpenIddictConstants.TokenTypes.AccessToken, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, OpenIddictConstants.TokenTypes.AuthorizationCode, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, OpenIddictConstants.TokenTypes.RefreshToken, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(type, OpenIddictConstants.TokenTypes.AccessToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, OpenIddictConstants.TokenTypes.AuthorizationCode, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, OpenIddictConstants.TokenTypes.RefreshToken, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException("The specified token type is not supported by the default token manager.", nameof(token)); + results.Add(new ValidationResult("The specified token type is not supported by the default token manager.")); } if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) { - throw new ArgumentException("The status cannot be null or empty.", nameof(token)); + results.Add(new ValidationResult("The status cannot be null or empty.")); } if (string.IsNullOrEmpty(await Store.GetSubjectAsync(token, cancellationToken))) { - throw new ArgumentException("The subject cannot be null or empty.", nameof(token)); + results.Add(new ValidationResult("The subject cannot be null or empty.")); } + + return results.ToImmutable(); + } + + /// + /// Populates the token using the specified descriptor. + /// + /// The token. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + protected virtual async Task PopulateAsync([NotNull] TToken token, + [NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + await Store.SetApplicationIdAsync(token, descriptor.ApplicationId, cancellationToken); + await Store.SetAuthorizationIdAsync(token, descriptor.AuthorizationId, cancellationToken); + await Store.SetCreationDateAsync(token, descriptor.CreationDate, cancellationToken); + await Store.SetExpirationDateAsync(token, descriptor.ExpirationDate, cancellationToken); + await Store.SetPayloadAsync(token, descriptor.Payload, cancellationToken); + await Store.SetReferenceIdAsync(token, descriptor.ReferenceId, cancellationToken); + await Store.SetStatusAsync(token, descriptor.Status, cancellationToken); + await Store.SetSubjectAsync(token, descriptor.Subject, cancellationToken); + await Store.SetTokenTypeAsync(token, descriptor.Type, cancellationToken); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index a66ab888..36bcb9b4 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -24,6 +24,7 @@ +