diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index 1c6340fb..9e917f21 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -47,34 +47,16 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
///
- public virtual async Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ public virtual Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
- if (application == null)
- {
- throw new ArgumentNullException(nameof(application));
- }
-
- if (!string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken)))
- {
- throw new ArgumentException("The client secret hash cannot be directly set on the application entity. " +
- "To create a confidential application, use the CreateAsync() overload accepting a secret parameter.");
- }
-
- // If no client type was specified, assume it's a public application.
- if (string.IsNullOrEmpty(await Store.GetClientTypeAsync(application, cancellationToken)))
- {
- await Store.SetClientTypeAsync(application, OpenIddictConstants.ClientTypes.Public, cancellationToken);
- }
-
- await ValidateAsync(application, cancellationToken);
- return await Store.CreateAsync(application, cancellationToken);
+ return CreateAsync(application, /* secret: */ null, cancellationToken);
}
///
- /// Creates a new confidential application, using the specified client secret.
+ /// Creates a new application.
///
/// The application to create.
- /// The client secret associated with the application.
+ /// The client secret associated with the application, if applicable.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
@@ -82,7 +64,7 @@ namespace OpenIddict.Core
///
public virtual async Task CreateAsync(
[NotNull] TApplication application,
- [NotNull] string secret, CancellationToken cancellationToken)
+ [CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
@@ -94,16 +76,32 @@ namespace OpenIddict.Core
throw new ArgumentException("The client secret hash cannot be directly set on the application entity.");
}
+ // If no client type was specified, assume it's a public application if no secret was provided.
var type = await Store.GetClientTypeAsync(application, cancellationToken);
- if (!string.IsNullOrEmpty(type) &&
- !string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrEmpty(type))
+ {
+ await Store.SetClientTypeAsync(application, string.IsNullOrEmpty(secret) ?
+ OpenIddictConstants.ClientTypes.Public :
+ OpenIddictConstants.ClientTypes.Confidential, cancellationToken);
+ }
+
+ if (string.IsNullOrEmpty(secret))
{
- throw new InvalidOperationException("The client type must be set to 'confidential' when creating an application with a client secret." +
- "To create a public application, use the CreateAsync() overload that doesn't take a secret parameter.");
+ // If the client is a confidential application, throw an
+ // exception as the client secret is required in this case.
+ if (string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException("A client secret must be provided when creating a confidential application.");
+ }
+
+ await Store.SetHashedSecretAsync(application, null, cancellationToken);
+ }
+
+ else
+ {
+ await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
}
- await Store.SetClientTypeAsync(application, OpenIddictConstants.ClientTypes.Confidential, cancellationToken);
- await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
await ValidateAsync(application, cancellationToken);
return await Store.CreateAsync(application, cancellationToken);
@@ -347,7 +345,7 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation.
///
- public virtual async Task SetClientSecretAsync([NotNull] TApplication application, [NotNull] string secret, CancellationToken cancellationToken)
+ public virtual async Task SetClientSecretAsync([NotNull] TApplication application, [CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
@@ -356,10 +354,14 @@ namespace OpenIddict.Core
if (string.IsNullOrEmpty(secret))
{
- throw new ArgumentException("The client secret cannot be null or empty.", nameof(secret));
+ await Store.SetHashedSecretAsync(application, null, cancellationToken);
+ }
+
+ else
+ {
+ await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
}
- await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
await UpdateAsync(application, cancellationToken);
}
@@ -382,6 +384,36 @@ namespace OpenIddict.Core
await Store.UpdateAsync(application, cancellationToken);
}
+ ///
+ /// Updates an existing application.
+ ///
+ /// The application to update.
+ /// The client secret associated with the application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual async Task UpdateAsync([NotNull] TApplication application,
+ [CanBeNull] string secret, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (string.IsNullOrEmpty(secret))
+ {
+ await Store.SetHashedSecretAsync(application, null, cancellationToken);
+ }
+
+ else
+ {
+ await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
+ }
+
+ await UpdateAsync(application, cancellationToken);
+ }
+
///
/// Validates the application to ensure it's in a consistent state.
///
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
index c0e77d39..1a212ff0 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
@@ -178,7 +178,7 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation.
///
- Task SetHashedSecretAsync([NotNull] TApplication application, [NotNull] string hash, CancellationToken cancellationToken);
+ Task SetHashedSecretAsync([NotNull] TApplication application, [CanBeNull] string hash, CancellationToken cancellationToken);
///
/// Updates an existing application.
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
index 78e34f7d..264489db 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
@@ -368,18 +368,14 @@ namespace OpenIddict.EntityFrameworkCore
///
/// A that can be used to monitor the asynchronous operation.
///
- public virtual Task SetHashedSecretAsync([NotNull] TApplication application, [NotNull] string hash, CancellationToken cancellationToken)
+ public virtual Task SetHashedSecretAsync([NotNull] TApplication application,
+ [CanBeNull] string hash, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
- if (string.IsNullOrEmpty(hash))
- {
- throw new ArgumentException("The client secret hash cannot be null or empty.", nameof(hash));
- }
-
application.ClientSecret = hash;
return Task.FromResult(0);