diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs
new file mode 100644
index 00000000..5738113a
--- /dev/null
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs
@@ -0,0 +1,45 @@
+namespace OpenIddict.Core
+{
+ ///
+ /// Represents an OpenIddict application descriptor.
+ ///
+ public class OpenIddictApplicationDescriptor
+ {
+ ///
+ /// Gets or sets the client identifier
+ /// associated with the application.
+ ///
+ public virtual string ClientId { get; set; }
+
+ ///
+ /// Gets or sets the client secret associated with the application.
+ /// Note: depending on the application manager used when creating it,
+ /// this property may be hashed or encrypted for security reasons.
+ ///
+ public virtual string ClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the display name
+ /// associated with the application.
+ ///
+ public virtual string DisplayName { get; set; }
+
+ ///
+ /// Gets or sets the logout callback URL
+ /// associated with the application.
+ ///
+ public virtual string LogoutRedirectUri { get; set; }
+
+ ///
+ /// Gets or sets the callback URL
+ /// associated with the application.
+ ///
+ public virtual string RedirectUri { get; set; }
+
+ ///
+ /// Gets or sets the application type
+ /// associated with the application.
+ ///
+ public virtual string Type { get; set; }
+ }
+}
diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index 25b4fd06..6bc8b742 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -94,29 +94,69 @@ namespace OpenIddict.Core
OpenIddictConstants.ClientTypes.Confidential, cancellationToken);
}
- if (string.IsNullOrEmpty(secret))
+ // If the client is a confidential application, throw an
+ // exception as the client secret is required in this case.
+ if (string.IsNullOrEmpty(secret) &&
+ string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
{
- // 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.SetClientSecretAsync(application, null, cancellationToken);
+ throw new InvalidOperationException("A client secret must be provided when creating a confidential application.");
}
- else
+ if (!string.IsNullOrEmpty(secret))
{
secret = await ObfuscateClientSecretAsync(secret, cancellationToken);
await Store.SetClientSecretAsync(application, secret, cancellationToken);
}
await ValidateAsync(application, cancellationToken);
-
return await Store.CreateAsync(application, cancellationToken);
}
+ ///
+ /// Creates a new application.
+ /// Note: the default implementation automatically hashes the client
+ /// secret before storing it in the database, for security reasons.
+ ///
+ /// The application descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// 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] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ // If no client type was specified, assume it's a
+ // public application if no secret was provided.
+ if (string.IsNullOrEmpty(descriptor.Type))
+ {
+ descriptor.Type = string.IsNullOrEmpty(descriptor.ClientSecret) ?
+ OpenIddictConstants.ClientTypes.Public :
+ OpenIddictConstants.ClientTypes.Confidential;
+ }
+
+ // If the client is a confidential application, throw an
+ // exception as the client secret is required in this case.
+ if (string.IsNullOrEmpty(descriptor.ClientSecret) &&
+ string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException("A client secret must be provided when creating a confidential application.");
+ }
+
+ // Obfuscate the provided client secret.
+ if (!string.IsNullOrEmpty(descriptor.ClientSecret))
+ {
+ descriptor.ClientSecret = await ObfuscateClientSecretAsync(descriptor.ClientSecret, cancellationToken);
+ }
+
+ await ValidateAsync(descriptor, cancellationToken);
+ return await Store.CreateAsync(descriptor, cancellationToken);
+ }
+
///
/// Removes an existing application.
///
@@ -409,6 +449,7 @@ namespace OpenIddict.Core
await Store.SetClientSecretAsync(application, secret, cancellationToken);
}
+ await ValidateAsync(application, cancellationToken);
await UpdateAsync(application, cancellationToken);
}
@@ -525,53 +566,72 @@ namespace OpenIddict.Core
///
protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
- if (application == null)
+ var descriptor = new OpenIddictApplicationDescriptor
{
- throw new ArgumentNullException(nameof(application));
+ ClientId = await Store.GetClientIdAsync(application, cancellationToken),
+ ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken),
+ DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken),
+ LogoutRedirectUri = await Store.GetLogoutRedirectUriAsync(application, cancellationToken),
+ RedirectUri = await Store.GetRedirectUriAsync(application, cancellationToken),
+ Type = await Store.GetClientTypeAsync(application, cancellationToken)
+ };
+
+ await ValidateAsync(descriptor, cancellationToken);
+ }
+
+ ///
+ /// Validates the application descriptor to ensure it's in a consistent state.
+ ///
+ /// The application descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ protected virtual Task ValidateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
}
- if (string.IsNullOrEmpty(await Store.GetClientIdAsync(application, cancellationToken)))
+ if (string.IsNullOrEmpty(descriptor.ClientId))
{
- throw new ArgumentException("The client identifier cannot be null or empty.", nameof(application));
+ throw new ArgumentException("The client identifier cannot be null or empty.", nameof(descriptor));
}
- var type = await Store.GetClientTypeAsync(application, cancellationToken);
- if (string.IsNullOrEmpty(type))
+ if (string.IsNullOrEmpty(descriptor.Type))
{
- throw new ArgumentException("The client type cannot be null or empty.", nameof(application));
+ throw new ArgumentException("The client type cannot be null or empty.", nameof(descriptor));
}
// Ensure the application type is supported by the manager.
- if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Only 'confidential' or 'public' applications are " +
- "supported by the default application manager.", nameof(application));
+ "supported by the default application manager.", nameof(descriptor));
}
- var secret = await Store.GetClientSecretAsync(application, cancellationToken);
-
// Ensure a client secret was specified if the client is a confidential application.
- if (string.IsNullOrEmpty(secret) &&
- string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrEmpty(descriptor.ClientSecret) &&
+ string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(application));
+ throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(descriptor));
}
// 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))
+ else if (!string.IsNullOrEmpty(descriptor.ClientSecret) &&
+ string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(application));
+ throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(descriptor));
}
// When a redirect_uri is specified, ensure it is valid and spec-compliant.
// See https://tools.ietf.org/html/rfc6749#section-3.1 for more information.
- var address = await Store.GetRedirectUriAsync(application, cancellationToken);
- if (!string.IsNullOrEmpty(address))
+ if (!string.IsNullOrEmpty(descriptor.RedirectUri))
{
// Ensure the redirect_uri is a valid and absolute URL.
- if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri))
+ if (!Uri.TryCreate(descriptor.RedirectUri, UriKind.Absolute, out Uri uri))
{
throw new ArgumentException("The redirect_uri must be an absolute URL.");
}
@@ -584,11 +644,10 @@ namespace OpenIddict.Core
}
// When a post_logout_redirect_uri is specified, ensure it is valid.
- address = await Store.GetLogoutRedirectUriAsync(application, cancellationToken);
- if (!string.IsNullOrEmpty(address))
+ if (!string.IsNullOrEmpty(descriptor.LogoutRedirectUri))
{
// Ensure the post_logout_redirect_uri is a valid and absolute URL.
- if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri))
+ if (!Uri.TryCreate(descriptor.LogoutRedirectUri, UriKind.Absolute, out Uri uri))
{
throw new ArgumentException("The post_logout_redirect_uri must be an absolute URL.");
}
@@ -599,6 +658,8 @@ namespace OpenIddict.Core
throw new ArgumentException("The post_logout_redirect_uri cannot contain a fragment.");
}
}
+
+ return Task.CompletedTask;
}
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index cd249bb0..e0be8bdd 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -5,7 +5,6 @@
*/
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -52,14 +51,15 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
///
- public virtual Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
+ public virtual async Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
- return Store.CreateAsync(authorization, cancellationToken);
+ await ValidateAsync(authorization, cancellationToken);
+ return await Store.CreateAsync(authorization, cancellationToken);
}
///
@@ -70,14 +70,15 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
///
- public virtual Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
+ public virtual async Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
- return Store.CreateAsync(descriptor, cancellationToken);
+ await ValidateAsync(descriptor, cancellationToken);
+ return await Store.CreateAsync(descriptor, cancellationToken);
}
///
@@ -145,6 +146,7 @@ namespace OpenIddict.Core
if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
{
await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken);
+ await ValidateAsync(authorization, cancellationToken);
await UpdateAsync(authorization, cancellationToken);
}
}
@@ -157,14 +159,15 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation.
///
- public virtual Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
+ public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
- return Store.UpdateAsync(authorization, cancellationToken);
+ await ValidateAsync(authorization, cancellationToken);
+ await Store.UpdateAsync(authorization, cancellationToken);
}
///
@@ -182,10 +185,35 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(authorization));
}
- if (string.IsNullOrEmpty(await Store.GetSubjectAsync(authorization, cancellationToken)))
+ var descriptor = new OpenIddictAuthorizationDescriptor
+ {
+ Subject = await Store.GetSubjectAsync(authorization, cancellationToken)
+ };
+
+ await ValidateAsync(descriptor, cancellationToken);
+ }
+
+ ///
+ /// Validates the authorization descriptor to ensure it's in a consistent state.
+ ///
+ /// The authorization descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ protected virtual Task ValidateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ if (string.IsNullOrEmpty(descriptor.Subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
}
+
+ return Task.CompletedTask;
}
}
}
\ No newline at end of file
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
index 88281ae9..70f7b4e9 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
@@ -33,6 +33,16 @@ namespace OpenIddict.Core
///
Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
+ ///
+ /// Creates a new application.
+ ///
+ /// The application descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the application.
+ ///
+ Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken);
+
///
/// Removes an existing application.
///
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
index 90c4fb05..a344f528 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
@@ -105,6 +105,34 @@ namespace OpenIddict.EntityFrameworkCore
return application;
}
+ ///
+ /// Creates a new application.
+ ///
+ /// The application descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the application.
+ ///
+ public Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ var application = new TApplication
+ {
+ ClientId = descriptor.ClientId,
+ ClientSecret = descriptor.ClientSecret,
+ DisplayName = descriptor.DisplayName,
+ LogoutRedirectUri = descriptor.LogoutRedirectUri,
+ RedirectUri = descriptor.RedirectUri,
+ Type = descriptor.Type
+ };
+
+ return CreateAsync(application, cancellationToken);
+ }
+
///
/// Removes an existing application.
///