Browse Source

Make the ValidateAsync() manager methods public and update them to return ValidationResult instances

pull/555/head
Kévin Chalet 8 years ago
parent
commit
75e6237996
  1. 1
      build/dependencies.props
  2. 235
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  3. 139
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  4. 66
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  5. 133
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  6. 1
      src/OpenIddict.Core/OpenIddict.Core.csproj

1
build/dependencies.props

@ -6,6 +6,7 @@
<AspNetCoreVersion>2.0.0</AspNetCoreVersion> <AspNetCoreVersion>2.0.0</AspNetCoreVersion>
<CoreFxVersion>4.4.0</CoreFxVersion> <CoreFxVersion>4.4.0</CoreFxVersion>
<CryptoHelperVersion>3.0.1.1</CryptoHelperVersion> <CryptoHelperVersion>3.0.1.1</CryptoHelperVersion>
<DataAnnotationsVersion>4.4.0</DataAnnotationsVersion>
<EntityFrameworkVersion>6.1.3</EntityFrameworkVersion> <EntityFrameworkVersion>6.1.3</EntityFrameworkVersion>
<JetBrainsVersion>10.3.0</JetBrainsVersion> <JetBrainsVersion>10.3.0</JetBrainsVersion>
<JsonNetVersion>10.0.2</JsonNetVersion> <JsonNetVersion>10.0.2</JsonNetVersion>

235
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -133,19 +134,13 @@ namespace OpenIddict.Core
await Store.SetClientSecretAsync(application, secret, cancellationToken); await Store.SetClientSecretAsync(application, secret, cancellationToken);
} }
await ValidateAsync(application, cancellationToken); var results = await ValidateAsync(application, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
try
{ {
await Store.CreateAsync(application, cancellationToken); throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application);
} }
catch (Exception exception) await Store.CreateAsync(application, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to create a new application.");
throw;
}
} }
/// <summary> /// <summary>
@ -197,24 +192,14 @@ namespace OpenIddict.Core
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns> /// </returns>
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) public virtual Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken = default)
{ {
if (application == null) if (application == null)
{ {
throw new ArgumentNullException(nameof(application)); throw new ArgumentNullException(nameof(application));
} }
try return Store.DeleteAsync(application, cancellationToken);
{
await Store.DeleteAsync(application, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(exception, "An exception occurred while trying to delete an existing application.");
throw;
}
} }
/// <summary> /// <summary>
@ -639,19 +624,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(application)); throw new ArgumentNullException(nameof(application));
} }
await ValidateAsync(application, cancellationToken); var results = await ValidateAsync(application, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
try
{ {
await Store.UpdateAsync(application, cancellationToken); throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application);
} }
catch (Exception exception) await Store.UpdateAsync(application, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to update an existing application.");
throw;
}
} }
/// <summary> /// <summary>
@ -684,7 +663,12 @@ namespace OpenIddict.Core
await Store.SetClientSecretAsync(application, secret, cancellationToken); 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); await UpdateAsync(application, cancellationToken);
} }
@ -767,6 +751,110 @@ namespace OpenIddict.Core
await UpdateAsync(application, cancellationToken); await UpdateAsync(application, cancellationToken);
} }
/// <summary>
/// Validates the application to ensure it's in a consistent state.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the validation error encountered when validating the application.
/// </returns>
public virtual async Task<ImmutableArray<ValidationResult>> ValidateAsync(
[NotNull] TApplication application, CancellationToken cancellationToken = default)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var results = ImmutableArray.CreateBuilder<ValidationResult>();
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<string>()
.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();
}
/// <summary> /// <summary>
/// Validates the client_secret associated with an application. /// Validates the client_secret associated with an application.
/// </summary> /// </summary>
@ -928,87 +1016,6 @@ namespace OpenIddict.Core
descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken); descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken);
} }
/// <summary>
/// Validates the application to ensure it's in a consistent state.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
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<string>()
.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.");
}
}
}
/// <summary> /// <summary>
/// Obfuscates the specified client secret so it can be safely stored in a database. /// 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. /// By default, this method returns a complex hashed representation computed using PBKDF2.

139
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -93,19 +94,13 @@ namespace OpenIddict.Core
await Store.SetTypeAsync(authorization, OpenIddictConstants.AuthorizationTypes.Permanent, cancellationToken); await Store.SetTypeAsync(authorization, OpenIddictConstants.AuthorizationTypes.Permanent, cancellationToken);
} }
await ValidateAsync(authorization, cancellationToken); var results = await ValidateAsync(authorization, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
try
{ {
await Store.CreateAsync(authorization, cancellationToken); throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization);
} }
catch (Exception exception) await Store.CreateAsync(authorization, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to create a new authorization.");
throw;
}
} }
/// <summary> /// <summary>
@ -144,24 +139,14 @@ namespace OpenIddict.Core
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns> /// </returns>
public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) public virtual Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default)
{ {
if (authorization == null) if (authorization == null)
{ {
throw new ArgumentNullException(nameof(authorization)); throw new ArgumentNullException(nameof(authorization));
} }
try return Store.DeleteAsync(authorization, cancellationToken);
{
await Store.DeleteAsync(authorization, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(exception, "An exception occurred while trying to delete an existing authorization.");
throw;
}
} }
/// <summary> /// <summary>
@ -563,7 +548,13 @@ namespace OpenIddict.Core
if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
{ {
await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken); 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); await UpdateAsync(authorization, cancellationToken);
} }
} }
@ -604,19 +595,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(authorization)); throw new ArgumentNullException(nameof(authorization));
} }
await ValidateAsync(authorization, cancellationToken); var results = await ValidateAsync(authorization, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
try
{ {
await Store.UpdateAsync(authorization, cancellationToken); throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization);
} }
catch (Exception exception) await Store.UpdateAsync(authorization, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to update an existing authorization.");
throw;
}
} }
/// <summary> /// <summary>
@ -654,70 +639,45 @@ namespace OpenIddict.Core
await UpdateAsync(authorization, cancellationToken); await UpdateAsync(authorization, cancellationToken);
} }
/// <summary>
/// Populates the authorization using the specified descriptor.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
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);
}
/// <summary> /// <summary>
/// Validates the authorization to ensure it's in a consistent state. /// Validates the authorization to ensure it's in a consistent state.
/// </summary> /// </summary>
/// <param name="authorization">The authorization.</param> /// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the validation error encountered when validating the authorization.
/// </returns> /// </returns>
protected virtual async Task ValidateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) public virtual async Task<ImmutableArray<ValidationResult>> ValidateAsync(
[NotNull] TAuthorization authorization, CancellationToken cancellationToken = default)
{ {
if (authorization == null) if (authorization == null)
{ {
throw new ArgumentNullException(nameof(authorization)); throw new ArgumentNullException(nameof(authorization));
} }
var results = ImmutableArray.CreateBuilder<ValidationResult>();
var type = await Store.GetTypeAsync(authorization, cancellationToken); var type = await Store.GetTypeAsync(authorization, cancellationToken);
if (string.IsNullOrEmpty(type)) 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) && else if (!string.Equals(type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIddictConstants.AuthorizationTypes.Permanent, 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))) 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))) 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. // 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)) 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)) 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();
}
/// <summary>
/// Populates the authorization using the specified descriptor.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
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);
} }
} }
} }

66
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -87,17 +88,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(scope)); 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) await Store.CreateAsync(scope, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to create a new scope.");
throw;
}
} }
/// <summary> /// <summary>
@ -136,24 +133,14 @@ namespace OpenIddict.Core
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns> /// </returns>
public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken = default) public virtual Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
{ {
if (scope == null) if (scope == null)
{ {
throw new ArgumentNullException(nameof(scope)); throw new ArgumentNullException(nameof(scope));
} }
try return Store.DeleteAsync(scope, cancellationToken);
{
await Store.DeleteAsync(scope, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(exception, "An exception occurred while trying to delete an existing scope.");
throw;
}
} }
/// <summary> /// <summary>
@ -305,17 +292,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(scope)); 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) await Store.UpdateAsync(scope, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to update an existing scope.");
throw;
}
} }
/// <summary> /// <summary>
@ -346,6 +329,33 @@ namespace OpenIddict.Core
await UpdateAsync(scope, cancellationToken); await UpdateAsync(scope, cancellationToken);
} }
/// <summary>
/// Validates the scope to ensure it's in a consistent state.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the validation error encountered when validating the scope.
/// </returns>
public virtual async Task<ImmutableArray<ValidationResult>> ValidateAsync(
[NotNull] TScope scope, CancellationToken cancellationToken = default)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
var results = ImmutableArray.CreateBuilder<ValidationResult>();
if (string.IsNullOrEmpty(await Store.GetNameAsync(scope, cancellationToken)))
{
results.Add(new ValidationResult("The scope name cannot be null or empty."));
}
return results.ToImmutable();
}
/// <summary> /// <summary>
/// Populates the scope using the specified descriptor. /// Populates the scope using the specified descriptor.
/// </summary> /// </summary>

133
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -89,19 +90,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(token)); throw new ArgumentNullException(nameof(token));
} }
await ValidateAsync(token, cancellationToken); var results = await ValidateAsync(token, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
try
{ {
await Store.CreateAsync(token, cancellationToken); throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, token);
} }
catch (Exception exception) await Store.CreateAsync(token, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to create a new token.");
throw;
}
} }
/// <summary> /// <summary>
@ -140,24 +135,14 @@ namespace OpenIddict.Core
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns> /// </returns>
public virtual async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken = default) public virtual Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken = default)
{ {
if (token == null) if (token == null)
{ {
throw new ArgumentNullException(nameof(token)); throw new ArgumentNullException(nameof(token));
} }
try return Store.DeleteAsync(token, cancellationToken);
{
await Store.DeleteAsync(token, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(exception, "An exception occurred while trying to delete an existing token.");
throw;
}
} }
/// <summary> /// <summary>
@ -737,17 +722,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(token)); 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) await Store.UpdateAsync(token, cancellationToken);
{
Logger.LogError(exception, "An exception occurred while trying to update an existing token.");
throw;
}
} }
/// <summary> /// <summary>
@ -785,54 +766,25 @@ namespace OpenIddict.Core
await UpdateAsync(token, cancellationToken); await UpdateAsync(token, cancellationToken);
} }
/// <summary>
/// Populates the token using the specified descriptor.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
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);
}
/// <summary> /// <summary>
/// Validates the token to ensure it's in a consistent state. /// Validates the token to ensure it's in a consistent state.
/// </summary> /// </summary>
/// <param name="token">The token.</param> /// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation. /// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the validation error encountered when validating the token.
/// </returns> /// </returns>
protected virtual async Task ValidateAsync([NotNull] TToken token, CancellationToken cancellationToken = default) public virtual async Task<ImmutableArray<ValidationResult>> ValidateAsync(
[NotNull] TToken token, CancellationToken cancellationToken = default)
{ {
if (token == null) if (token == null)
{ {
throw new ArgumentNullException(nameof(token)); throw new ArgumentNullException(nameof(token));
} }
var results = ImmutableArray.CreateBuilder<ValidationResult>();
// If a reference identifier was associated with the token, // If a reference identifier was associated with the token,
// ensure it's not already used for a different token. // ensure it's not already used for a different token.
var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); var identifier = await Store.GetReferenceIdAsync(token, cancellationToken);
@ -843,32 +795,67 @@ namespace OpenIddict.Core
await Store.GetIdAsync(other, cancellationToken), await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal)) 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); var type = await Store.GetTokenTypeAsync(token, cancellationToken);
if (string.IsNullOrEmpty(type)) 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) && else if (!string.Equals(type, OpenIddictConstants.TokenTypes.AccessToken, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIddictConstants.TokenTypes.AuthorizationCode, StringComparison.OrdinalIgnoreCase) && !string.Equals(type, OpenIddictConstants.TokenTypes.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIddictConstants.TokenTypes.RefreshToken, 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))) 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))) 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();
}
/// <summary>
/// Populates the token using the specified descriptor.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
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);
} }
} }
} }

1
src/OpenIddict.Core/OpenIddict.Core.csproj

@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" /> <PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(ImmutableCollectionsVersion)" /> <PackageReference Include="System.Collections.Immutable" Version="$(ImmutableCollectionsVersion)" />
<PackageReference Include="System.ComponentModel.Annotations" Version="$(DataAnnotationsVersion)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Loading…
Cancel
Save