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 @@
+