diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 2fa5a4db..4ebdbed8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -129,6 +129,7 @@ namespace OpenIddict.Core "a confidential or hybrid application."); } + // If a client secret was provided, obfuscate it. if (!string.IsNullOrEmpty(secret)) { secret = await ObfuscateClientSecretAsync(secret, cancellationToken); @@ -151,7 +152,7 @@ namespace OpenIddict.Core } /// - /// Creates a new application. + /// Creates a new application based on the specified descriptor. /// Note: the default implementation automatically hashes the client /// secret before storing it in the database, for security reasons. /// @@ -168,43 +169,23 @@ namespace OpenIddict.Core 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 not a public application, throw an - // exception as the client secret is required in this case. - if (string.IsNullOrEmpty(descriptor.ClientSecret) && - !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("A client secret must be provided when creating " + - "a confidential or hybrid application."); - } - - // Obfuscate the provided client secret. - if (!string.IsNullOrEmpty(descriptor.ClientSecret)) + var application = await Store.InstantiateAsync(cancellationToken); + if (application == null) { - descriptor.ClientSecret = await ObfuscateClientSecretAsync(descriptor.ClientSecret, cancellationToken); + throw new InvalidOperationException("An error occurred while trying to create a new application"); } - await ValidateAsync(descriptor, cancellationToken); + await PopulateAsync(application, descriptor, cancellationToken); - try + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + if (!string.IsNullOrEmpty(secret)) { - return await Store.CreateAsync(descriptor, cancellationToken); + await Store.SetClientSecretAsync(application, /* secret: */ null, cancellationToken); + return await CreateAsync(application, secret, cancellationToken); } - catch (Exception exception) - { - Logger.LogError(0, exception, "An exception occurred while trying to create a new application."); - throw; - } + return await CreateAsync(application, cancellationToken); } /// @@ -584,6 +565,83 @@ namespace OpenIddict.Core await UpdateAsync(application, cancellationToken); } + /// + /// Updates an existing application. + /// + /// The application to update. + /// The delegate used to update the application based on the given descriptor. + /// 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, + [NotNull] Func operation, CancellationToken cancellationToken) + { + if (operation == null) + { + throw new ArgumentNullException(nameof(operation)); + } + + // Store the original client secret for later comparison. + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + + var descriptor = new OpenIddictApplicationDescriptor + { + ClientId = await Store.GetClientIdAsync(application, cancellationToken), + ClientSecret = secret, + DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken), + Type = await Store.GetClientTypeAsync(application, cancellationToken) + }; + + foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(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."); + } + + descriptor.PostLogoutRedirectUris.Add(uri); + } + + foreach (var address in 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."); + } + + descriptor.RedirectUris.Add(uri); + } + + await operation(descriptor); + await PopulateAsync(application, descriptor, cancellationToken); + + // If the client secret was updated, re-obfuscate it before persisting the changes. + var comparand = await Store.GetClientSecretAsync(application, cancellationToken); + if (!string.Equals(secret, comparand, StringComparison.Ordinal)) + { + await UpdateAsync(application, comparand, cancellationToken); + + return; + } + + await UpdateAsync(application, cancellationToken); + } + /// /// Validates the client_secret associated with an application. /// @@ -639,8 +697,7 @@ namespace OpenIddict.Core /// A that can be used to monitor the asynchronous operation, whose result /// returns a boolean indicating whether the post_logout_redirect_uri was valid. /// - public virtual async Task ValidatePostLogoutRedirectUriAsync( - [NotNull] string address, CancellationToken cancellationToken) + public virtual async Task ValidatePostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(address)) { @@ -707,120 +764,96 @@ namespace OpenIddict.Core } /// - /// Validates the application to ensure it's in a consistent state. + /// Populates the application using the specified descriptor. /// /// The application. + /// 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 ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken) + protected virtual async Task PopulateAsync([NotNull] TApplication application, + [NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) { - var descriptor = new OpenIddictApplicationDescriptor - { - ClientId = await Store.GetClientIdAsync(application, cancellationToken), - ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken), - DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken), - Type = await Store.GetClientTypeAsync(application, cancellationToken) - }; - - foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) + if (application == null) { - // 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."); - } - - descriptor.PostLogoutRedirectUris.Add(uri); + throw new ArgumentNullException(nameof(application)); } - foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken)) + if (descriptor == null) { - // 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."); - } - - descriptor.RedirectUris.Add(uri); + throw new ArgumentNullException(nameof(descriptor)); } - await ValidateAsync(descriptor, cancellationToken); + await Store.SetClientIdAsync(application, descriptor.ClientId, cancellationToken); + await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken); + await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken); + await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken); + await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange( + descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken); + await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange( + descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken); } /// - /// Validates the application descriptor to ensure it's in a consistent state. + /// Validates the application to ensure it's in a consistent state. /// - /// The application descriptor. + /// The application. /// 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) + protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken) { - if (descriptor == null) + if (string.IsNullOrEmpty(await Store.GetClientIdAsync(application, cancellationToken))) { - throw new ArgumentNullException(nameof(descriptor)); + throw new ArgumentException("The client identifier cannot be null or empty.", nameof(application)); } - if (string.IsNullOrEmpty(descriptor.ClientId)) - { - throw new ArgumentException("The client identifier cannot be null or empty.", nameof(descriptor)); - } - - if (string.IsNullOrEmpty(descriptor.Type)) + var type = await Store.GetClientTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The client type cannot be null or empty.", nameof(descriptor)); + 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(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) && - !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Hybrid, StringComparison.OrdinalIgnoreCase) && - !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + 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(descriptor)); + "supported by the default application manager.", nameof(application)); } // Ensure a client secret was specified if the client is a confidential application. - if (string.IsNullOrEmpty(descriptor.ClientSecret) && - string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) + 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(descriptor)); + 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(descriptor.ClientSecret) && - string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + 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(descriptor)); + 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 uri in descriptor.PostLogoutRedirectUris.Concat(descriptor.RedirectUris)) + foreach (var address in ImmutableArray.Create() + .AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) + .AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) { - // Ensure the address is not null. - if (uri == null) + // Ensure the address is not null or empty. + if (string.IsNullOrEmpty(address)) { - throw new ArgumentException("Callback URLs cannot be null."); + throw new ArgumentException("Callback URLs cannot be null or empty."); } - // Ensure the address is a valid and absolute URL. - if (!uri.IsAbsoluteUri || !uri.IsWellFormedOriginalString()) + // 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."); } @@ -831,8 +864,6 @@ namespace OpenIddict.Core throw new ArgumentException("Callback URLs cannot contain a fragment."); } } - - return Task.FromResult(0); } /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index f718916c..02bd5ff6 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -108,7 +108,7 @@ namespace OpenIddict.Core } /// - /// Creates a new authorization. + /// Creates a new authorization based on the specified descriptor. /// /// The authorization descriptor. /// The that can be used to abort the operation. @@ -122,26 +122,14 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(descriptor)); } - // If no type was explicitly specified, assume that - // the authorization is a permanent authorization. - if (string.IsNullOrEmpty(descriptor.Type)) - { - descriptor.Type = OpenIddictConstants.AuthorizationTypes.Permanent; - } - - await ValidateAsync(descriptor, cancellationToken); - - try + var authorization = await Store.InstantiateAsync(cancellationToken); + if (authorization == null) { - return await Store.CreateAsync(descriptor, cancellationToken); + throw new InvalidOperationException("An error occurred while trying to create a new authorization."); } - catch (Exception exception) - { - Logger.LogError(0, exception, "An exception occurred while trying to create a new authorization."); - - throw; - } + await PopulateAsync(authorization, descriptor, cancellationToken); + return await CreateAsync(authorization, cancellationToken); } /// @@ -462,81 +450,119 @@ namespace OpenIddict.Core } /// - /// Validates the authorization to ensure it's in a consistent state. + /// Updates an existing authorization. /// - /// The authorization. + /// The authorization to update. + /// The delegate used to update the authorization based on the given descriptor. /// 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] TAuthorization authorization, CancellationToken cancellationToken) + public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, + [NotNull] Func operation, CancellationToken cancellationToken) { - if (authorization == null) + if (operation == null) { - throw new ArgumentNullException(nameof(authorization)); + throw new ArgumentNullException(nameof(operation)); } var descriptor = new OpenIddictAuthorizationDescriptor { + ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken), Status = await Store.GetStatusAsync(authorization, cancellationToken), Subject = await Store.GetSubjectAsync(authorization, cancellationToken), Type = await Store.GetTypeAsync(authorization, cancellationToken) }; - await ValidateAsync(descriptor, cancellationToken); + foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) + { + descriptor.Scopes.Add(scope); + } + + await operation(descriptor); + await PopulateAsync(authorization, descriptor, cancellationToken); + await UpdateAsync(authorization, cancellationToken); } /// - /// Validates the authorization descriptor to ensure it's in a consistent state. + /// Populates the authorization using the specified descriptor. /// - /// The authorization 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 Task ValidateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) + protected virtual async Task PopulateAsync([NotNull] TAuthorization authorization, + [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } - if (string.IsNullOrEmpty(descriptor.Type)) + 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. + /// + protected virtual async Task ValidateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + var type = await Store.GetTypeAsync(authorization, cancellationToken); + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The authorization type cannot be null or empty.", nameof(descriptor)); + throw new ArgumentException("The authorization type cannot be null or empty.", nameof(authorization)); } - if (!string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && - !string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) + 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."); + throw new ArgumentException("The specified authorization type is not supported by the default token manager.", nameof(authorization)); } - if (string.IsNullOrEmpty(descriptor.Status)) + if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) { - throw new ArgumentException("The status cannot be null or empty."); + throw new ArgumentException("The status cannot be null or empty.", nameof(authorization)); } - if (string.IsNullOrEmpty(descriptor.Subject)) + if (string.IsNullOrEmpty(await Store.GetSubjectAsync(authorization, cancellationToken))) { - throw new ArgumentException("The subject cannot be null or empty."); + throw new ArgumentException("The subject cannot be null or empty.", nameof(authorization)); } // Ensure that the scopes are not null or empty and do not contain spaces. - foreach (var scope in descriptor.Scopes) + foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) { if (string.IsNullOrEmpty(scope)) { - throw new ArgumentException("Scopes cannot be null or empty.", nameof(descriptor)); + throw new ArgumentException("Scopes cannot be null or empty.", nameof(authorization)); } if (scope.Contains(OpenIddictConstants.Separators.Space)) { - throw new ArgumentException("Scopes cannot contain spaces.", nameof(descriptor)); + throw new ArgumentException("Scopes cannot contain spaces.", nameof(authorization)); } } - - return Task.FromResult(0); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index 84b7fa2f..0a39576d 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -100,7 +100,7 @@ namespace OpenIddict.Core } /// - /// Creates a new scope. + /// Creates a new scope based on the specified descriptor. /// /// The scope descriptor. /// The that can be used to abort the operation. @@ -114,17 +114,14 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(descriptor)); } - try + var scope = await Store.InstantiateAsync(cancellationToken); + if (scope == null) { - return await Store.CreateAsync(descriptor, cancellationToken); + throw new InvalidOperationException("An error occurred while trying to create a new scope."); } - catch (Exception exception) - { - Logger.LogError(0, exception, "An exception occurred while trying to create a new scope."); - - throw; - } + await PopulateAsync(scope, descriptor, cancellationToken); + return await CreateAsync(scope, cancellationToken); } /// @@ -237,5 +234,59 @@ namespace OpenIddict.Core throw; } } + + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The delegate used to update the scope based on the given descriptor. + /// 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] TScope scope, + [NotNull] Func operation, CancellationToken cancellationToken) + { + if (operation == null) + { + throw new ArgumentNullException(nameof(operation)); + } + + var descriptor = new OpenIddictScopeDescriptor + { + Description = await Store.GetDescriptionAsync(scope, cancellationToken), + Name = await Store.GetNameAsync(scope, cancellationToken) + }; + + await operation(descriptor); + await PopulateAsync(scope, descriptor, cancellationToken); + await UpdateAsync(scope, cancellationToken); + } + + /// + /// Populates the scope using the specified descriptor. + /// + /// The scope. + /// 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] TScope scope, + [NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + await Store.SetDescriptionAsync(scope, descriptor.Description, cancellationToken); + await Store.SetNameAsync(scope, descriptor.Description, cancellationToken); + } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index f97de828..cf3f8277 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -102,7 +102,7 @@ namespace OpenIddict.Core } /// - /// Creates a new token, which is associated with a particular subject. + /// Creates a new token based on the specified descriptor. /// /// The token descriptor. /// The that can be used to abort the operation. @@ -116,19 +116,14 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(descriptor)); } - await ValidateAsync(descriptor, cancellationToken); - - try + var token = await Store.InstantiateAsync(cancellationToken); + if (token == null) { - return await Store.CreateAsync(descriptor, cancellationToken); + throw new InvalidOperationException("An error occurred while trying to create a new token"); } - catch (Exception exception) - { - Logger.LogError(0, exception, "An exception occurred while trying to create a new token."); - - throw; - } + await PopulateAsync(token, descriptor, cancellationToken); + return await CreateAsync(token, cancellationToken); } /// @@ -674,68 +669,110 @@ namespace OpenIddict.Core } /// - /// Validates the token to ensure it's in a consistent state. + /// Updates an existing token. /// - /// The token. + /// The token to update. + /// The delegate used to update the token based on the given descriptor. /// 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] TToken token, CancellationToken cancellationToken) + public virtual async Task UpdateAsync([NotNull] TToken token, + [NotNull] Func operation, CancellationToken cancellationToken) { - if (token == null) + if (operation == null) { - throw new ArgumentNullException(nameof(token)); + throw new ArgumentNullException(nameof(operation)); } var descriptor = new OpenIddictTokenDescriptor { + ApplicationId = await Store.GetApplicationIdAsync(token, cancellationToken), + AuthorizationId = await Store.GetAuthorizationIdAsync(token, cancellationToken), + Ciphertext = await Store.GetCiphertextAsync(token, cancellationToken), + CreationDate = await Store.GetCreationDateAsync(token, cancellationToken), + ExpirationDate = await Store.GetExpirationDateAsync(token, cancellationToken), + Hash = await Store.GetHashAsync(token, cancellationToken), Status = await Store.GetStatusAsync(token, cancellationToken), Subject = await Store.GetSubjectAsync(token, cancellationToken), Type = await Store.GetTokenTypeAsync(token, cancellationToken) }; - await ValidateAsync(descriptor, cancellationToken); + await operation(descriptor); + await PopulateAsync(token, descriptor, cancellationToken); + await UpdateAsync(token, cancellationToken); } /// - /// Validates the token descriptor to ensure it's in a consistent state. + /// Populates the token using the specified descriptor. /// - /// The token 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 Task ValidateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) + protected virtual async Task PopulateAsync([NotNull] TToken token, + [NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } - if (string.IsNullOrEmpty(descriptor.Type)) + await Store.SetApplicationIdAsync(token, descriptor.ApplicationId, cancellationToken); + await Store.SetAuthorizationIdAsync(token, descriptor.AuthorizationId, cancellationToken); + await Store.SetCiphertextAsync(token, descriptor.Ciphertext, cancellationToken); + await Store.SetCreationDateAsync(token, descriptor.CreationDate, cancellationToken); + await Store.SetExpirationDateAsync(token, descriptor.ExpirationDate, cancellationToken); + await Store.SetHashAsync(token, descriptor.Hash, 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. + /// + protected virtual async Task ValidateAsync([NotNull] TToken token, CancellationToken cancellationToken) + { + if (token == null) { - throw new ArgumentException("The token type cannot be null or empty.", nameof(descriptor)); + throw new ArgumentNullException(nameof(token)); } - if (!string.Equals(descriptor.Type, OpenIddictConstants.TokenTypes.AccessToken, StringComparison.OrdinalIgnoreCase) && - !string.Equals(descriptor.Type, OpenIddictConstants.TokenTypes.AuthorizationCode, StringComparison.OrdinalIgnoreCase) && - !string.Equals(descriptor.Type, OpenIddictConstants.TokenTypes.RefreshToken, StringComparison.OrdinalIgnoreCase)) + var type = await Store.GetTokenTypeAsync(token, cancellationToken); + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The specified token type is not supported by the default token manager."); + throw new ArgumentException("The token type cannot be null or empty.", nameof(token)); } - if (string.IsNullOrEmpty(descriptor.Status)) + 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 status cannot be null or empty."); + throw new ArgumentException("The specified token type is not supported by the default token manager.", nameof(token)); } - if (string.IsNullOrEmpty(descriptor.Subject)) + if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) { - throw new ArgumentException("The subject cannot be null or empty."); + throw new ArgumentException("The status cannot be null or empty.", nameof(token)); } - return Task.FromResult(0); + if (string.IsNullOrEmpty(await Store.GetSubjectAsync(token, cancellationToken))) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(token)); + } } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index c8a2a055..ea5f5645 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -52,16 +52,6 @@ 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. /// @@ -207,6 +197,16 @@ namespace OpenIddict.Core /// Task> GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken); + /// + /// Instantiates a new application. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the instantiated application, that can be persisted in the database. + /// + Task InstantiateAsync(CancellationToken cancellationToken); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -231,6 +231,17 @@ namespace OpenIddict.Core /// Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Sets the client identifier associated with an application. + /// + /// The application. + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetClientIdAsync([NotNull] TApplication application, [CanBeNull] string identifier, CancellationToken cancellationToken); + /// /// Sets the client secret associated with an application. /// Note: depending on the manager used to create the application, @@ -253,7 +264,18 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken); + Task SetClientTypeAsync([NotNull] TApplication application, [CanBeNull] string type, CancellationToken cancellationToken); + + /// + /// Sets the display name associated with an application. + /// + /// The application. + /// The display name associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetDisplayNameAsync([NotNull] TApplication application, [CanBeNull] string name, CancellationToken cancellationToken); /// /// Sets the logout callback addresses associated with an application. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index 7d02ddc6..1310b146 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -51,16 +51,6 @@ namespace OpenIddict.Core /// Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); - /// - /// Creates a new authorization. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes an existing authorization. /// @@ -129,6 +119,17 @@ namespace OpenIddict.Core /// Task GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// + /// Retrieves the scopes associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes associated with the specified authorization. + /// + Task> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// /// Retrieves the status associated with an authorization. /// @@ -162,6 +163,16 @@ namespace OpenIddict.Core /// Task GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// + /// Instantiates a new authorization. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the instantiated authorization, that can be persisted in the database. + /// + Task InstantiateAsync(CancellationToken cancellationToken); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -208,7 +219,20 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken); + Task SetApplicationIdAsync([NotNull] TAuthorization authorization, + [CanBeNull] string identifier, CancellationToken cancellationToken); + + /// + /// Sets the scopes associated with an authorization. + /// + /// The authorization. + /// The scopes associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetScopesAsync([NotNull] TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken); /// /// Sets the status associated with an authorization. @@ -219,7 +243,20 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken); + Task SetStatusAsync([NotNull] TAuthorization authorization, + [CanBeNull] string status, CancellationToken cancellationToken); + + /// + /// Sets the subject associated with an authorization. + /// + /// The authorization. + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetSubjectAsync([NotNull] TAuthorization authorization, + [CanBeNull] string subject, CancellationToken cancellationToken); /// /// Sets the type associated with an authorization. @@ -230,7 +267,8 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - Task SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken); + Task SetTypeAsync([NotNull] TAuthorization authorization, + [CanBeNull] string type, CancellationToken cancellationToken); /// /// Updates an existing authorization. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs index 22b3d760..f71c1afd 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs @@ -51,16 +51,6 @@ namespace OpenIddict.Core /// Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken); - /// - /// Creates a new scope. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - Task CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes an existing scope. /// @@ -83,6 +73,38 @@ namespace OpenIddict.Core /// Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Retrieves the description associated with a scope. + /// + /// 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 description associated with the specified scope. + /// + Task GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken); + + /// + /// Retrieves the name associated with a scope. + /// + /// 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 name associated with the specified scope. + /// + Task GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken); + + /// + /// Instantiates a new scope. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated scope, that can be persisted in the database. + /// + Task InstantiateAsync(CancellationToken cancellationToken); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -107,6 +129,28 @@ namespace OpenIddict.Core /// Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Sets the description associated with a scope. + /// + /// The scope. + /// The description associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken); + + /// + /// Sets the name associated with a scope. + /// + /// The scope. + /// The name associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken); + /// /// Updates an existing scope. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index 51c871c0..75007225 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -51,16 +51,6 @@ namespace OpenIddict.Core /// Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken); - /// - /// Creates a new token. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes a token. /// @@ -103,7 +93,7 @@ namespace OpenIddict.Core Task FindByHashAsync([NotNull] string hash, CancellationToken cancellationToken); /// - /// Retrieves an token using its unique identifier. + /// Retrieves a token using its unique identifier. /// /// The unique identifier associated with the token. /// The that can be used to abort the operation. @@ -246,6 +236,16 @@ namespace OpenIddict.Core /// Task GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken); + /// + /// Instantiates a new token. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated token, that can be persisted in the database. + /// + Task InstantiateAsync(CancellationToken cancellationToken); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -287,7 +287,7 @@ namespace OpenIddict.Core /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// The unique identifier associated with the token. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. @@ -298,24 +298,57 @@ namespace OpenIddict.Core /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the token. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + /// + /// Sets the ciphertext associated with a token. + /// + /// The token. + /// The ciphertext associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetCiphertextAsync([NotNull] TToken token, [CanBeNull] string ciphertext, CancellationToken cancellationToken); + + /// + /// Sets the creation date associated with a token. + /// + /// The token. + /// The creation date. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetCreationDateAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken); + /// /// Sets the expiration date associated with a token. /// /// The token. - /// The date on which the token will no longer be considered valid. + /// The expiration date. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// Task SetExpirationDateAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken); + /// + /// Sets the hash associated with a token. + /// + /// The token. + /// The hash associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetHashAsync([NotNull] TToken token, [CanBeNull] string hash, CancellationToken cancellationToken); + /// /// Sets the status associated with a token. /// @@ -325,7 +358,29 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - Task SetStatusAsync([NotNull] TToken token, [NotNull] string status, CancellationToken cancellationToken); + Task SetStatusAsync([NotNull] TToken token, [CanBeNull] string status, CancellationToken cancellationToken); + + /// + /// Sets the subject associated with a token. + /// + /// The token. + /// The subject associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetSubjectAsync([NotNull] TToken token, [CanBeNull] string subject, CancellationToken cancellationToken); + + /// + /// Sets the token type associated with a token. + /// + /// The token. + /// The token type associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetTokenTypeAsync([NotNull] TToken token, [CanBeNull] string type, CancellationToken cancellationToken); /// /// Updates an existing token. diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index 25c02062..ccc0725f 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs @@ -64,16 +64,6 @@ namespace OpenIddict.Core /// public abstract 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. - /// - public abstract Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes an existing application. /// @@ -425,6 +415,16 @@ namespace OpenIddict.Core return Task.FromResult(ImmutableArray.Create(uris)); } + /// + /// Instantiates a new application. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the instantiated application, that can be persisted in the database. + /// + public virtual Task InstantiateAsync(CancellationToken cancellationToken) => Task.FromResult(new TApplication()); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -469,6 +469,28 @@ namespace OpenIddict.Core /// public abstract Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Sets the client identifier associated with an application. + /// + /// The application. + /// The client identifier 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 Task SetClientIdAsync([NotNull] TApplication application, + [CanBeNull] string identifier, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + application.ClientId = identifier; + + return Task.FromResult(0); + } + /// /// Sets the client secret associated with an application. /// Note: depending on the manager used to create the application, @@ -502,19 +524,37 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken) + public virtual Task SetClientTypeAsync([NotNull] TApplication application, + [CanBeNull] string type, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - if (string.IsNullOrEmpty(type)) + application.Type = type; + + return Task.FromResult(0); + } + + /// + /// Sets the display name associated with an application. + /// + /// The application. + /// The display name 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 Task SetDisplayNameAsync([NotNull] TApplication application, + [CanBeNull] string name, CancellationToken cancellationToken) + { + if (application == null) { - throw new ArgumentException("The client type cannot be null or empty.", nameof(type)); + throw new ArgumentNullException(nameof(application)); } - application.Type = type; + application.DisplayName = name; return Task.FromResult(0); } diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 0d7ec91c..4c020296 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -64,16 +64,6 @@ namespace OpenIddict.Core /// public abstract Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); - /// - /// Creates a new authorization. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - public abstract Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes an existing authorization. /// @@ -170,12 +160,15 @@ namespace OpenIddict.Core return ConvertIdentifierToString(authorization.Application.Id); } - var key = await GetAsync(authorizations => - from element in authorizations - where element.Id.Equals(authorization.Id) - select element.Application.Id, cancellationToken); + IQueryable Query(IQueryable authorizations) + { + return from element in authorizations + where element.Id.Equals(authorization.Id) + where element.Application != null + select element.Application.Id; + } - return ConvertIdentifierToString(key); + return ConvertIdentifierToString(await GetAsync(Query, cancellationToken)); } /// @@ -209,6 +202,34 @@ namespace OpenIddict.Core return Task.FromResult(ConvertIdentifierToString(authorization.Id)); } + /// + /// Retrieves the scopes associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes associated with the specified authorization. + /// + public virtual Task> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (string.IsNullOrEmpty(authorization.Scopes)) + { + return Task.FromResult(ImmutableArray.Create()); + } + + var scopes = authorization.Scopes.Split( + new[] { OpenIddictConstants.Separators.Space }, + StringSplitOptions.RemoveEmptyEntries); + + return Task.FromResult(ImmutableArray.Create(scopes)); + } + /// /// Retrieves the status associated with an authorization. /// @@ -261,6 +282,16 @@ namespace OpenIddict.Core return Task.FromResult(authorization.Type); } + /// + /// Instantiates a new authorization. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result + /// returns the instantiated authorization, that can be persisted in the database. + /// + public virtual Task InstantiateAsync(CancellationToken cancellationToken) => Task.FromResult(new TAuthorization()); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -352,7 +383,47 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public abstract Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken); + public abstract Task SetApplicationIdAsync([NotNull] TAuthorization authorization, + [CanBeNull] string identifier, CancellationToken cancellationToken); + + /// + /// Sets the scopes associated with an authorization. + /// + /// The authorization. + /// The scopes associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetScopesAsync([NotNull] TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (scopes.IsDefaultOrEmpty) + { + authorization.Scopes = null; + + return Task.FromResult(0); + } + + if (scopes.Any(scope => string.IsNullOrEmpty(scope))) + { + throw new ArgumentException("Scopes cannot be null or empty.", nameof(authorization)); + } + + if (scopes.Any(scope => scope.Contains(OpenIddictConstants.Separators.Space))) + { + throw new ArgumentException("Scopes cannot contain spaces.", nameof(authorization)); + } + + authorization.Scopes = string.Join(OpenIddictConstants.Separators.Space, scopes); + + return Task.FromResult(0); + } /// /// Sets the status associated with an authorization. @@ -363,7 +434,8 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken) + public virtual Task SetStatusAsync([NotNull] TAuthorization authorization, + [CanBeNull] string status, CancellationToken cancellationToken) { if (authorization == null) { @@ -375,6 +447,28 @@ namespace OpenIddict.Core return Task.FromResult(0); } + /// + /// Sets the subject associated with an authorization. + /// + /// The authorization. + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetSubjectAsync([NotNull] TAuthorization authorization, + [CanBeNull] string subject, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + authorization.Subject = subject; + + return Task.FromResult(0); + } + /// /// Sets the type associated with an authorization. /// @@ -384,7 +478,8 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual Task SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken) + public virtual Task SetTypeAsync([NotNull] TAuthorization authorization, + [CanBeNull] string type, CancellationToken cancellationToken) { if (authorization == null) { diff --git a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs index 7600a95b..1596f9d8 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs @@ -60,16 +60,6 @@ namespace OpenIddict.Core /// public abstract Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken); - /// - /// Creates a new scope. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - public abstract Task CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes an existing scope. /// @@ -92,6 +82,54 @@ namespace OpenIddict.Core /// public abstract Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Retrieves the description associated with a scope. + /// + /// 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 description associated with the specified scope. + /// + public virtual Task GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + return Task.FromResult(scope.Description); + } + + /// + /// Retrieves the name associated with a scope. + /// + /// 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 name associated with the specified scope. + /// + public virtual Task GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + return Task.FromResult(scope.Name); + } + + /// + /// Instantiates a new scope. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated scope, that can be persisted in the database. + /// + public virtual Task InstantiateAsync(CancellationToken cancellationToken) => Task.FromResult(new TScope()); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -136,6 +174,48 @@ namespace OpenIddict.Core /// public abstract Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Sets the description associated with a scope. + /// + /// The scope. + /// The description associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + scope.Description = description; + + return Task.FromResult(0); + } + + /// + /// Sets the name associated with a scope. + /// + /// The scope. + /// The name associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + scope.Name = name; + + return Task.FromResult(0); + } + /// /// Updates an existing scope. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index b60738a9..7e4b651c 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -64,16 +64,6 @@ namespace OpenIddict.Core /// public abstract Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken); - /// - /// Creates a new token. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - public abstract Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken); - /// /// Removes a token. /// @@ -167,7 +157,7 @@ namespace OpenIddict.Core } /// - /// Retrieves an token using its unique identifier. + /// Retrieves a token using its unique identifier. /// /// The unique identifier associated with the token. /// The that can be used to abort the operation. @@ -253,12 +243,15 @@ namespace OpenIddict.Core return ConvertIdentifierToString(token.Application.Id); } - var key = await GetAsync(tokens => - from element in tokens - where element.Id.Equals(token.Id) - select element.Application.Id, cancellationToken); + IQueryable Query(IQueryable tokens) + { + return from element in tokens + where element.Id.Equals(token.Id) + where element.Application != null + select element.Application.Id; + } - return ConvertIdentifierToString(key); + return ConvertIdentifierToString(await GetAsync(Query, cancellationToken)); } /// @@ -282,12 +275,15 @@ namespace OpenIddict.Core return ConvertIdentifierToString(token.Authorization.Id); } - var key = await GetAsync(tokens => - from element in tokens - where element.Id.Equals(token.Id) - select element.Authorization.Id, cancellationToken); + IQueryable Query(IQueryable tokens) + { + return from element in tokens + where element.Id.Equals(token.Id) + where element.Authorization != null + select element.Authorization.Id; + } - return ConvertIdentifierToString(key); + return ConvertIdentifierToString(await GetAsync(Query, cancellationToken)); } /// @@ -442,6 +438,16 @@ namespace OpenIddict.Core return Task.FromResult(token.Type); } + /// + /// Instantiates a new token. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated token, that can be persisted in the database. + /// + public virtual Task InstantiateAsync(CancellationToken cancellationToken) => Task.FromResult(new TToken()); + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -527,7 +533,7 @@ namespace OpenIddict.Core /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the token. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. @@ -538,18 +544,61 @@ namespace OpenIddict.Core /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// The unique identifier associated with the token. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public abstract Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + /// + /// Sets the ciphertext associated with a token. + /// + /// The token. + /// The ciphertext associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetCiphertextAsync([NotNull] TToken token, [CanBeNull] string ciphertext, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + token.Ciphertext = ciphertext; + + return Task.FromResult(0); + } + + /// + /// Sets the creation date associated with a token. + /// + /// The token. + /// The creation date. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetCreationDateAsync([NotNull] TToken token, + [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + token.CreationDate = date; + + return Task.FromResult(0); + } + /// /// Sets the expiration date associated with a token. /// /// The token. - /// The date on which the token will no longer be considered valid. + /// The expiration date. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. @@ -567,6 +616,27 @@ namespace OpenIddict.Core return Task.FromResult(0); } + /// + /// Sets the hash associated with a token. + /// + /// The token. + /// The hash associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetHashAsync([NotNull] TToken token, [CanBeNull] string hash, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + token.Hash = hash; + + return Task.FromResult(0); + } + /// /// Sets the status associated with a token. /// @@ -576,18 +646,75 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public virtual Task SetStatusAsync([NotNull] TToken token, [NotNull] string status, CancellationToken cancellationToken) + public virtual Task SetStatusAsync([NotNull] TToken token, [CanBeNull] string status, CancellationToken cancellationToken) { if (token == null) { throw new ArgumentNullException(nameof(token)); } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException("The status cannot be null or empty.", nameof(status)); + } + token.Status = status; return Task.FromResult(0); } + /// + /// Sets the subject associated with a token. + /// + /// The token. + /// The subject associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetSubjectAsync([NotNull] TToken token, [CanBeNull] string subject, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); + } + + token.Subject = subject; + + return Task.FromResult(0); + } + + /// + /// Sets the token type associated with a token. + /// + /// The token. + /// The token type associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetTokenTypeAsync([NotNull] TToken token, [CanBeNull] string type, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); + } + + token.Type = type; + + return Task.FromResult(0); + } + /// /// Updates an existing token. /// diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index 402d68e5..f215c10e 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -208,8 +208,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Entity() .HasMany(application => application.Tokens) .WithOptional(token => token.Application) - .Map(association => association.MapKey("ApplicationId")) - .WillCascadeOnDelete(); + .Map(association => association.MapKey("ApplicationId")); builder.Entity() .ToTable("OpenIddictApplications"); @@ -237,8 +236,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Entity() .HasMany(application => application.Tokens) .WithOptional(token => token.Authorization) - .Map(association => association.MapKey("AuthorizationId")) - .WillCascadeOnDelete(); + .Map(association => association.MapKey("AuthorizationId")); builder.Entity() .ToTable("OpenIddictAuthorizations"); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index ae1e81df..98646dfc 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -134,27 +134,6 @@ namespace OpenIddict.EntityFramework 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 override async Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var application = new TApplication(); - - await BindAsync(application, descriptor, cancellationToken); - return await CreateAsync(application, cancellationToken); - } - /// /// Removes an existing application. /// @@ -291,48 +270,5 @@ namespace OpenIddict.EntityFramework return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the application properties based on the specified descriptor. - /// - /// The application to update. - /// 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 BindAsync([NotNull] TApplication application, [NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - { - if (application == null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - application.ClientId = descriptor.ClientId; - application.ClientSecret = descriptor.ClientSecret; - application.DisplayName = descriptor.DisplayName; - application.Type = descriptor.Type; - - if (descriptor.PostLogoutRedirectUris.Count != 0) - { - application.PostLogoutRedirectUris = string.Join( - OpenIddictConstants.Separators.Space, - descriptor.PostLogoutRedirectUris.Select(uri => uri.OriginalString)); - } - - if (descriptor.RedirectUris.Count != 0) - { - application.RedirectUris = string.Join( - OpenIddictConstants.Separators.Space, - descriptor.RedirectUris.Select(uri => uri.OriginalString)); - } - - return Task.FromResult(0); - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index a081f839..efdb3847 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -134,27 +134,6 @@ namespace OpenIddict.EntityFramework return authorization; } - /// - /// Creates a new authorization. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - public override async Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var authorization = new TAuthorization(); - - await BindAsync(authorization, descriptor, cancellationToken); - return await CreateAsync(authorization, cancellationToken); - } - /// /// Removes an existing authorization. /// @@ -204,7 +183,17 @@ namespace OpenIddict.EntityFramework throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + var authorization = (from entry in Context.ChangeTracker.Entries() + where entry.Entity != null + where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + select entry.Entity).FirstOrDefault(); + + if (authorization != null) + { + return Task.FromResult(authorization); + } + + return base.FindByIdAsync(identifier, cancellationToken); } /// @@ -223,17 +212,21 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(authorization)); } - // If the application is not attached to the authorization instance (which is expected - // if the token was retrieved using the default FindBy*Async APIs as they don't - // eagerly load the application from the database), try to load it manually. + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application == null) + { + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) + { + return null; + } + + await reference.LoadAsync(cancellationToken); + } + if (authorization.Application == null) { - return ConvertIdentifierToString( - await Context.Entry(authorization) - .Reference(entry => entry.Application) - .Query() - .Select(application => application.Id) - .FirstOrDefaultAsync()); + return null; } return ConvertIdentifierToString(authorization.Application.Id); @@ -256,7 +249,7 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(query)); } - return query(Authorizations).FirstOrDefaultAsync(cancellationToken); + return query(Authorizations.Include(authorization => authorization.Application)).FirstOrDefaultAsync(cancellationToken); } /// @@ -276,7 +269,8 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(query)); } - return ImmutableArray.CreateRange(await query(Authorizations).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await query( + Authorizations.Include(authorization => authorization.Application)).ToListAsync(cancellationToken)); } /// @@ -308,15 +302,19 @@ namespace OpenIddict.EntityFramework else { - var key = await GetIdAsync(authorization, cancellationToken); - - // Try to retrieve the application associated with the authorization. - // If none can be found, assume that no application is attached. - var application = await Applications.FirstOrDefaultAsync(element => element.Authorizations.Any(t => t.Id.Equals(key))); - if (application != null) + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application == null) { - application.Authorizations.Remove(authorization); + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } + + await reference.LoadAsync(cancellationToken); } + + authorization.Application = null; } } @@ -345,48 +343,5 @@ namespace OpenIddict.EntityFramework return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the authorization properties based on the specified descriptor. - /// - /// The authorization to update. - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task BindAsync([NotNull] TAuthorization authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - { - if (authorization == null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - authorization.Status = descriptor.Status; - authorization.Subject = descriptor.Subject; - authorization.Type = descriptor.Type; - - if (descriptor.Scopes.Count != 0) - { - authorization.Scopes = string.Join(OpenIddictConstants.Separators.Space, descriptor.Scopes); - } - - // Bind the authorization to the specified application, if applicable. - if (!string.IsNullOrEmpty(descriptor.ApplicationId)) - { - var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId)); - if (application == null) - { - throw new InvalidOperationException("The application associated with the authorization cannot be found."); - } - - authorization.Application = application; - } - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index 56997071..7cd7492f 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs @@ -114,22 +114,6 @@ namespace OpenIddict.EntityFramework return scope; } - /// - /// Creates a new scope. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - public override async Task CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - { - var scope = new TScope(); - - await BindAsync(scope, descriptor, cancellationToken); - return await CreateAsync(scope, cancellationToken); - } - /// /// Removes an existing scope. /// @@ -215,32 +199,5 @@ namespace OpenIddict.EntityFramework return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the scope properties based on the specified descriptor. - /// - /// The scope to update. - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual Task BindAsync([NotNull] TScope scope, [NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - { - if (scope == null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - scope.Description = descriptor.Description; - scope.Name = descriptor.Name; - - return Task.FromResult(0); - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index ccd7c221..69cfe996 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -133,27 +133,6 @@ namespace OpenIddict.EntityFramework return token; } - /// - /// Creates a new token. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - public override async Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var token = new TToken(); - - await BindAsync(token, descriptor, cancellationToken); - return await CreateAsync(token, cancellationToken); - } - /// /// Removes a token. /// @@ -173,7 +152,7 @@ namespace OpenIddict.EntityFramework } /// - /// Retrieves an token using its unique identifier. + /// Retrieves a token using its unique identifier. /// /// The unique identifier associated with the token. /// The that can be used to abort the operation. @@ -188,7 +167,17 @@ namespace OpenIddict.EntityFramework throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return Tokens.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + var token = (from entry in Context.ChangeTracker.Entries() + where entry.Entity != null + where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + select entry.Entity).FirstOrDefault(); + + if (token != null) + { + return Task.FromResult(token); + } + + return base.FindByIdAsync(identifier, cancellationToken); } /// @@ -207,17 +196,21 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(token)); } - // If the application is not attached to the token instance (which is expected - // if the token was retrieved using the default FindBy*Async APIs as they don't - // eagerly load the application from the database), try to load it manually. + // If the application is not attached to the token, try to load it manually. if (token.Application == null) { - return ConvertIdentifierToString( - await Context.Entry(token) - .Reference(entry => entry.Application) - .Query() - .Select(application => application.Id) - .FirstOrDefaultAsync()); + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) + { + return null; + } + + await reference.LoadAsync(cancellationToken); + } + + if (token.Application == null) + { + return null; } return ConvertIdentifierToString(token.Application.Id); @@ -240,7 +233,9 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(query)); } - return query(Tokens).FirstOrDefaultAsync(cancellationToken); + return query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization)).FirstOrDefaultAsync(cancellationToken); } /// @@ -259,17 +254,21 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(token)); } - // If the authorization is not attached to the token instance (which is expected - // if the token was retrieved using the default FindBy*Async APIs as they don't - // eagerly load the authorization from the database), try to load it manually. + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization == null) + { + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) + { + return null; + } + + await reference.LoadAsync(cancellationToken); + } + if (token.Authorization == null) { - return ConvertIdentifierToString( - await Context.Entry(token) - .Reference(entry => entry.Authorization) - .Query() - .Select(authorization => authorization.Id) - .FirstOrDefaultAsync()); + return null; } return ConvertIdentifierToString(token.Authorization.Id); @@ -292,7 +291,9 @@ namespace OpenIddict.EntityFramework throw new ArgumentNullException(nameof(query)); } - return ImmutableArray.CreateRange(await query(Tokens).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization)).ToListAsync(cancellationToken)); } /// @@ -324,15 +325,19 @@ namespace OpenIddict.EntityFramework else { - var key = await GetIdAsync(token, cancellationToken); - - // Try to retrieve the application associated with the token. - // If none can be found, assume that no application is attached. - var application = await Applications.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key))); - if (application != null) + // If the application is not attached to the token, try to load it manually. + if (token.Application == null) { - application.Tokens.Remove(token); + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } + + await reference.LoadAsync(cancellationToken); } + + token.Application = null; } } @@ -365,15 +370,19 @@ namespace OpenIddict.EntityFramework else { - var key = await GetIdAsync(token, cancellationToken); - - // Try to retrieve the authorization associated with the token. - // If none can be found, assume that no authorization is attached. - var authorization = await Authorizations.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key))); - if (authorization != null) + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization == null) { - authorization.Tokens.Remove(token); + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } + + await reference.LoadAsync(cancellationToken); } + + token.Authorization = null; } } @@ -402,59 +411,5 @@ namespace OpenIddict.EntityFramework return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the token properties based on the specified descriptor. - /// - /// The token to update. - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task BindAsync([NotNull] TToken token, [NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - { - if (token == null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - token.Ciphertext = descriptor.Ciphertext; - token.CreationDate = descriptor.CreationDate; - token.ExpirationDate = descriptor.ExpirationDate; - token.Hash = descriptor.Hash; - token.Status = descriptor.Status; - token.Subject = descriptor.Subject; - token.Type = descriptor.Type; - - // Bind the token to the specified client application, if applicable. - if (!string.IsNullOrEmpty(descriptor.ApplicationId)) - { - var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId)); - if (application == null) - { - throw new InvalidOperationException("The application associated with the token cannot be found."); - } - - token.Application = application; - } - - // Bind the token to the specified authorization, if applicable. - if (!string.IsNullOrEmpty(descriptor.AuthorizationId)) - { - var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.AuthorizationId)); - if (authorization == null) - { - throw new InvalidOperationException("The authorization associated with the token cannot be found."); - } - - token.Authorization = authorization; - } - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs index fca70808..7bd1793a 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs @@ -243,8 +243,7 @@ namespace Microsoft.Extensions.DependencyInjection entity.HasMany(application => application.Tokens) .WithOne(token => token.Application) .HasForeignKey("ApplicationId") - .IsRequired(required: false) - .OnDelete(DeleteBehavior.Cascade); + .IsRequired(required: false); entity.ToTable("OpenIddictApplications"); }); @@ -269,8 +268,7 @@ namespace Microsoft.Extensions.DependencyInjection entity.HasMany(authorization => authorization.Tokens) .WithOne(token => token.Authorization) .HasForeignKey("AuthorizationId") - .IsRequired(required: false) - .OnDelete(DeleteBehavior.Cascade); + .IsRequired(required: false); entity.ToTable("OpenIddictAuthorizations"); }); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index 95d47f64..d1e3ee69 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -134,27 +134,6 @@ 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 override async Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var application = new TApplication(); - - await BindAsync(application, descriptor, cancellationToken); - return await CreateAsync(application, cancellationToken); - } - /// /// Removes an existing application. /// @@ -272,48 +251,5 @@ namespace OpenIddict.EntityFrameworkCore return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the application properties based on the specified descriptor. - /// - /// The application to update. - /// 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 BindAsync([NotNull] TApplication application, [NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - { - if (application == null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - application.ClientId = descriptor.ClientId; - application.ClientSecret = descriptor.ClientSecret; - application.DisplayName = descriptor.DisplayName; - application.Type = descriptor.Type; - - if (descriptor.PostLogoutRedirectUris.Count != 0) - { - application.PostLogoutRedirectUris = string.Join( - OpenIddictConstants.Separators.Space, - descriptor.PostLogoutRedirectUris.Select(uri => uri.OriginalString)); - } - - if (descriptor.RedirectUris.Count != 0) - { - application.RedirectUris = string.Join( - OpenIddictConstants.Separators.Space, - descriptor.RedirectUris.Select(uri => uri.OriginalString)); - } - - return Task.FromResult(0); - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 1982c00e..855f93e9 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -134,27 +134,6 @@ namespace OpenIddict.EntityFrameworkCore return authorization; } - /// - /// Creates a new authorization. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - public override async Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var authorization = new TAuthorization(); - - await BindAsync(authorization, descriptor, cancellationToken); - return await CreateAsync(authorization, cancellationToken); - } - /// /// Removes an existing authorization. /// @@ -220,7 +199,7 @@ namespace OpenIddict.EntityFrameworkCore { var key = ConvertIdentifierFromString(client); - return from authorization in authorizations + return from authorization in authorizations.Include(authorization => authorization.Application) where authorization.Subject == subject join application in applications on authorization.Application.Id equals application.Id where application.Id.Equals(key) @@ -230,6 +209,35 @@ namespace OpenIddict.EntityFrameworkCore return ImmutableArray.CreateRange(await Query(Authorizations, Applications).ToListAsync(cancellationToken)); } + /// + /// Retrieves an authorization using its unique identifier. + /// + /// The unique identifier associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization corresponding to the identifier. + /// + public override Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); + } + + var authorization = (from entry in Context.ChangeTracker.Entries() + where entry.Entity != null + where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + select entry.Entity).FirstOrDefault(); + + if (authorization != null) + { + return Task.FromResult(authorization); + } + + return base.FindByIdAsync(identifier, cancellationToken); + } + /// /// Executes the specified query and returns the first element. /// @@ -247,7 +255,7 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentNullException(nameof(query)); } - return query(Authorizations).FirstOrDefaultAsync(cancellationToken); + return query(Authorizations.Include(authorization => authorization.Application)).FirstOrDefaultAsync(cancellationToken); } /// @@ -267,7 +275,8 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentNullException(nameof(query)); } - return ImmutableArray.CreateRange(await query(Authorizations).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await query( + Authorizations.Include(authorization => authorization.Application)).ToListAsync(cancellationToken)); } /// @@ -338,50 +347,5 @@ namespace OpenIddict.EntityFrameworkCore return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the authorization properties based on the specified descriptor. - /// - /// The authorization to update. - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task BindAsync([NotNull] TAuthorization authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - { - if (authorization == null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - authorization.Status = descriptor.Status; - authorization.Subject = descriptor.Subject; - authorization.Type = descriptor.Type; - - if (descriptor.Scopes.Count != 0) - { - authorization.Scopes = string.Join(OpenIddictConstants.Separators.Space, descriptor.Scopes); - } - - // Bind the authorization to the specified application, if applicable. - if (!string.IsNullOrEmpty(descriptor.ApplicationId)) - { - var key = ConvertIdentifierFromString(descriptor.ApplicationId); - - var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key)); - if (application == null) - { - throw new InvalidOperationException("The application associated with the authorization cannot be found."); - } - - authorization.Application = application; - } - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index 16e07e8b..abe7cca1 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs @@ -114,22 +114,6 @@ namespace OpenIddict.EntityFrameworkCore return scope; } - /// - /// Creates a new scope. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - public override async Task CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - { - var scope = new TScope(); - - await BindAsync(scope, descriptor, cancellationToken); - return await CreateAsync(scope, cancellationToken); - } - /// /// Removes an existing scope. /// @@ -215,32 +199,5 @@ namespace OpenIddict.EntityFrameworkCore return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the scope properties based on the specified descriptor. - /// - /// The scope to update. - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual Task BindAsync([NotNull] TScope scope, [NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - { - if (scope == null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - scope.Description = descriptor.Description; - scope.Name = descriptor.Name; - - return Task.FromResult(0); - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index f421aff7..a3a2e8dc 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -133,27 +133,6 @@ namespace OpenIddict.EntityFrameworkCore return token; } - /// - /// Creates a new token. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - public override async Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var token = new TToken(); - - await BindAsync(token, descriptor, cancellationToken); - return await CreateAsync(token, cancellationToken); - } - /// /// Removes a token. /// @@ -197,7 +176,7 @@ namespace OpenIddict.EntityFrameworkCore { var key = ConvertIdentifierFromString(identifier); - return from token in tokens + return from token in tokens.Include(token => token.Application).Include(token => token.Authorization) join application in applications on token.Application.Id equals application.Id where application.Id.Equals(key) select token; @@ -231,7 +210,7 @@ namespace OpenIddict.EntityFrameworkCore { var key = ConvertIdentifierFromString(identifier); - return from token in tokens + return from token in tokens.Include(token => token.Application).Include(token => token.Authorization) join authorization in authorizations on token.Authorization.Id equals authorization.Id where authorization.Id.Equals(key) select token; @@ -240,6 +219,35 @@ namespace OpenIddict.EntityFrameworkCore return ImmutableArray.CreateRange(await Query(Authorizations, Tokens).ToListAsync(cancellationToken)); } + /// + /// Retrieves a token using its unique identifier. + /// + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// + public override Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); + } + + var token = (from entry in Context.ChangeTracker.Entries() + where entry.Entity != null + where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + select entry.Entity).FirstOrDefault(); + + if (token != null) + { + return Task.FromResult(token); + } + + return base.FindByIdAsync(identifier, cancellationToken); + } + /// /// Executes the specified query and returns the first element. /// @@ -257,7 +265,9 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentNullException(nameof(query)); } - return query(Tokens).FirstOrDefaultAsync(cancellationToken); + return query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization)).FirstOrDefaultAsync(cancellationToken); } /// @@ -277,7 +287,9 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentNullException(nameof(query)); } - return ImmutableArray.CreateRange(await query(Tokens).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization)).ToListAsync(cancellationToken)); } /// @@ -391,63 +403,5 @@ namespace OpenIddict.EntityFrameworkCore return Context.SaveChangesAsync(cancellationToken); } - - /// - /// Sets the token properties based on the specified descriptor. - /// - /// The token to update. - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual async Task BindAsync([NotNull] TToken token, [NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - { - if (token == null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - token.Ciphertext = descriptor.Ciphertext; - token.CreationDate = descriptor.CreationDate; - token.ExpirationDate = descriptor.ExpirationDate; - token.Hash = descriptor.Hash; - token.Status = descriptor.Status; - token.Subject = descriptor.Subject; - token.Type = descriptor.Type; - - // Bind the token to the specified client application, if applicable. - if (!string.IsNullOrEmpty(descriptor.ApplicationId)) - { - var key = ConvertIdentifierFromString(descriptor.ApplicationId); - - var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key)); - if (application == null) - { - throw new InvalidOperationException("The application associated with the token cannot be found."); - } - - token.Application = application; - } - - // Bind the token to the specified authorization, if applicable. - if (!string.IsNullOrEmpty(descriptor.AuthorizationId)) - { - var key = ConvertIdentifierFromString(descriptor.AuthorizationId); - - var authorization = await Authorizations.SingleOrDefaultAsync(entity => entity.Id.Equals(key)); - if (authorization == null) - { - throw new InvalidOperationException("The authorization associated with the token cannot be found."); - } - - token.Authorization = authorization; - } - } } } \ No newline at end of file