|
|
|
@ -44,12 +44,67 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns the unique identifier associated with the application.
|
|
|
|
/// </returns>
|
|
|
|
public virtual Task<string> CreateAsync(TApplication application, CancellationToken cancellationToken) { |
|
|
|
public virtual async Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
return Store.CreateAsync(application, cancellationToken); |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new confidential application, using the specified client secret.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="application">The application to create.</param>
|
|
|
|
/// <param name="secret">The client secret associated with 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 unique identifier associated with the application.
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<string> CreateAsync( |
|
|
|
[NotNull] TApplication application, |
|
|
|
[NotNull] string secret, 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."); |
|
|
|
} |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Removes an existing application.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="application">The application to delete.</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>
|
|
|
|
public virtual Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
return Store.DeleteAsync(application, cancellationToken); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -100,7 +155,7 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns the client type of the application (by default, "public").
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<string> GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) { |
|
|
|
public virtual async Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
@ -126,7 +181,7 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns the display name associated with the application.
|
|
|
|
/// </returns>
|
|
|
|
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) { |
|
|
|
public virtual Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
@ -143,13 +198,48 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns the tokens associated with the application.
|
|
|
|
/// </returns>
|
|
|
|
public virtual Task<IEnumerable<string>> GetTokensAsync(TApplication application, CancellationToken cancellationToken) { |
|
|
|
public virtual Task<IEnumerable<string>> GetTokensAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
return Store.GetTokensAsync(application, cancellationToken); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Determines whether the specified application has a redirect_uri.
|
|
|
|
/// </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 a boolean indicating whether a redirect_uri is registered.
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<bool> HasRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
return !string.IsNullOrEmpty(await Store.GetRedirectUriAsync(application, cancellationToken)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Determines whether the specified application has a client secret.
|
|
|
|
/// </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 a boolean indicating whether a client secret is registered.
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<bool> HasClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
return !string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Determines whether an application is a confidential client.
|
|
|
|
/// </summary>
|
|
|
|
@ -175,7 +265,7 @@ namespace OpenIddict.Core { |
|
|
|
/// <param name="application">The application.</param>
|
|
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
|
|
/// <returns><c>true</c> if the application is a public client, <c>false</c> otherwise.</returns>
|
|
|
|
public async Task<bool> IsPublicAsync(TApplication application, CancellationToken cancellationToken) { |
|
|
|
public async Task<bool> IsPublicAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
@ -189,6 +279,105 @@ namespace OpenIddict.Core { |
|
|
|
return string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Updates the client secret associated with an application.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="application">The application.</param>
|
|
|
|
/// <param name="secret">The client secret associated with 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>
|
|
|
|
public virtual async Task SetClientSecretAsync([NotNull] TApplication application, [NotNull] string secret, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(secret)) { |
|
|
|
throw new ArgumentException("The client secret cannot be null or empty.", nameof(secret)); |
|
|
|
} |
|
|
|
|
|
|
|
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken); |
|
|
|
await UpdateAsync(application, cancellationToken); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Updates an existing application.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="application">The application to update.</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>
|
|
|
|
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
await ValidateAsync(application, cancellationToken); |
|
|
|
await Store.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.
|
|
|
|
/// </returns>
|
|
|
|
protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(await Store.GetClientIdAsync(application, cancellationToken))) { |
|
|
|
throw new ArgumentException("The client identifier cannot be null or empty.", 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.Public, StringComparison.OrdinalIgnoreCase)) { |
|
|
|
throw new ArgumentException("Only 'confidential' or 'public' applications are " + |
|
|
|
"supported by the default application manager.", nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
var hash = await Store.GetHashedSecretAsync(application, cancellationToken); |
|
|
|
|
|
|
|
// Ensure a client secret was specified if the client is a confidential application.
|
|
|
|
if (string.IsNullOrEmpty(hash) && |
|
|
|
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(hash) && |
|
|
|
string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) { |
|
|
|
throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(application)); |
|
|
|
} |
|
|
|
|
|
|
|
// 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)) { |
|
|
|
Uri uri; |
|
|
|
// Ensure the redirect_uri is a valid and absolute URL.
|
|
|
|
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)) { |
|
|
|
throw new ArgumentException("The redirect_uri must be an absolute URL."); |
|
|
|
} |
|
|
|
|
|
|
|
// Ensure the redirect_uri doesn't contain a fragment.
|
|
|
|
if (!string.IsNullOrEmpty(uri.Fragment)) { |
|
|
|
throw new ArgumentException("The redirect_uri cannot contain a fragment."); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Validates the redirect_uri associated with an application.
|
|
|
|
/// </summary>
|
|
|
|
@ -199,7 +388,7 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns a boolean indicating whether the redirect_uri was valid.
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address, CancellationToken cancellationToken) { |
|
|
|
public virtual async Task<bool> ValidateRedirectUriAsync([NotNull] TApplication application, string address, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
@ -225,7 +414,7 @@ namespace OpenIddict.Core { |
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns a boolean indicating whether the client secret was valid.
|
|
|
|
/// </returns>
|
|
|
|
public virtual async Task<bool> ValidateSecretAsync(TApplication application, string secret, CancellationToken cancellationToken) { |
|
|
|
public virtual async Task<bool> ValidateClientSecretAsync([NotNull] TApplication application, string secret, CancellationToken cancellationToken) { |
|
|
|
if (application == null) { |
|
|
|
throw new ArgumentNullException(nameof(application)); |
|
|
|
} |
|
|
|
|