diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index c70f73e6..c2ba9021 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -201,6 +201,25 @@ namespace OpenIddict.Core return Store.FindByIdAsync(identifier, cancellationToken); } + /// + /// Retrieves the optional application identifier 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 application identifier associated with the authorization. + /// + public virtual Task GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + return Store.GetApplicationIdAsync(authorization, cancellationToken); + } + /// /// Executes the specified query. /// @@ -338,6 +357,22 @@ namespace OpenIddict.Core return Store.ListAsync(query, cancellationToken); } + /// + /// Lists the ad-hoc authorizations that are marked as invalid or have no + /// valid token attached and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + public virtual Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken) + { + return Store.ListInvalidAsync(count, offset, cancellationToken); + } + /// /// Revokes an authorization. /// @@ -360,6 +395,26 @@ namespace OpenIddict.Core } } + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + await Store.SetApplicationIdAsync(authorization, identifier, cancellationToken); + await UpdateAsync(authorization, cancellationToken); + } + /// /// Updates an existing authorization. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 244a55da..540be6d3 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -236,6 +236,25 @@ namespace OpenIddict.Core return Store.FindBySubjectAsync(subject, cancellationToken); } + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// 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 application identifier associated with the token. + /// + public virtual Task GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetApplicationIdAsync(token, cancellationToken); + } + /// /// Executes the specified query. /// @@ -490,6 +509,22 @@ namespace OpenIddict.Core return Store.ListAsync(query, cancellationToken); } + /// + /// Lists the tokens that are marked as expired or invalid + /// and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + public virtual Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken) + { + return Store.ListInvalidAsync(count, offset, cancellationToken); + } + /// /// Redeems a token. /// @@ -533,42 +568,42 @@ namespace OpenIddict.Core } /// - /// Sets the authorization associated with a token. + /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the client application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// - public virtual async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public virtual async Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { throw new ArgumentNullException(nameof(token)); } - await Store.SetAuthorizationAsync(token, identifier, cancellationToken); + await Store.SetApplicationIdAsync(token, identifier, cancellationToken); await UpdateAsync(token, cancellationToken); } /// - /// Sets the client application associated with a token. + /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// 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. /// - public virtual async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public virtual async Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { throw new ArgumentNullException(nameof(token)); } - await Store.SetClientAsync(token, identifier, cancellationToken); + await Store.SetAuthorizationIdAsync(token, identifier, cancellationToken); await UpdateAsync(token, cancellationToken); } diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index 6ce29b16..d4fb16d8 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -94,6 +94,17 @@ namespace OpenIddict.Core /// Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken); + /// + /// Retrieves the optional application identifier 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 application identifier associated with the authorization. + /// + Task GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + /// /// Executes the specified query. /// @@ -174,6 +185,30 @@ namespace OpenIddict.Core /// Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Lists the ad-hoc authorizations that are marked as invalid or have no + /// valid token attached and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken); + + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken); + /// /// Sets the status associated with an authorization. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index b5ed0a47..d35402f9 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -113,6 +113,17 @@ namespace OpenIddict.Core /// Task> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken); + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// 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 application identifier associated with the token. + /// + Task GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken); + /// /// Executes the specified query. /// @@ -249,26 +260,39 @@ namespace OpenIddict.Core Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); /// - /// Sets the authorization associated with a token. + /// Lists the tokens that are marked as expired or invalid + /// and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken); + + /// + /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the client application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// - Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); /// - /// Sets the client application associated with a token. + /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// 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. /// - Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); /// /// Sets the expiration date associated with a token. diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 04e1af64..10a5b9f7 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -136,6 +136,35 @@ namespace OpenIddict.Core return GetAsync(authorizations => authorizations.Where(authorization => authorization.Id.Equals(key)), cancellationToken); } + /// + /// Retrieves the optional application identifier 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 application identifier associated with the authorization. + /// + public virtual async Task GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (authorization.Application != null) + { + 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); + + return ConvertIdentifierToString(key); + } + /// /// Executes the specified query. /// @@ -263,6 +292,55 @@ namespace OpenIddict.Core /// public abstract Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Lists the ad-hoc authorizations that are marked as invalid or have no + /// valid token attached and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + public virtual Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken) + { + IQueryable Query(IQueryable authorizations) + { + var query = (from authorization in authorizations + where authorization.Status != OpenIddictConstants.Statuses.Valid || + (authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && + !authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid)) + orderby authorization.Id + select authorization).AsQueryable(); + + if (offset.HasValue) + { + query = query.Skip(offset.Value); + } + + if (count.HasValue) + { + query = query.Take(count.Value); + } + + return query; + } + + return ListAsync(Query, cancellationToken); + } + + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public abstract Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken); + /// /// Sets the status associated with an authorization. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index b5c468ed..c6dc11d4 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -174,6 +174,35 @@ namespace OpenIddict.Core /// public abstract Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// 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 application identifier associated with the token. + /// + public virtual async Task GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + if (token.Application != null) + { + 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); + + return ConvertIdentifierToString(key); + } + /// /// Retrieves the optional authorization identifier associated with a token. /// @@ -400,7 +429,44 @@ namespace OpenIddict.Core public abstract Task> ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken); /// - /// Sets the authorization associated with a token. + /// Lists the tokens that are marked as expired or invalid + /// and that can be safely removed from the database. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the elements returned when executing the specified query. + /// + public virtual Task> ListInvalidAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken) + { + IQueryable Query(IQueryable tokens) + { + var query = (from token in tokens + where token.ExpirationDate < DateTimeOffset.UtcNow || + token.Status != OpenIddictConstants.Statuses.Valid + orderby token.Id + select token).AsQueryable(); + + if (offset.HasValue) + { + query = query.Skip(offset.Value); + } + + if (count.HasValue) + { + query = query.Take(count.Value); + } + + return query; + } + + return ListAsync(Query, cancellationToken); + } + + /// + /// Sets the authorization identifier associated with a token. /// /// The token. /// The unique identifier associated with the authorization. @@ -408,10 +474,10 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public abstract Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + public abstract Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); /// - /// Sets the client application associated with a token. + /// Sets the application identifier associated with a token. /// /// The token. /// The unique identifier associated with the client application. @@ -419,7 +485,7 @@ namespace OpenIddict.Core /// /// A that can be used to monitor the asynchronous operation. /// - public abstract Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); + public abstract Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken); /// /// Sets the expiration date associated with a token. diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index d0f7115d..fb008fce 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -210,7 +210,8 @@ namespace Microsoft.Extensions.DependencyInjection builder.Entity() .HasMany(application => application.Tokens) .WithOptional(token => token.Application) - .Map(association => association.MapKey("ApplicationId")); + .Map(association => association.MapKey("ApplicationId")) + .WillCascadeOnDelete(); builder.Entity() .ToTable("OpenIddictApplications"); @@ -240,7 +241,8 @@ namespace Microsoft.Extensions.DependencyInjection builder.Entity() .HasMany(application => application.Tokens) .WithOptional(token => token.Authorization) - .Map(association => association.MapKey("AuthorizationId")); + .Map(association => association.MapKey("AuthorizationId")) + .WillCascadeOnDelete(); builder.Entity() .ToTable("OpenIddictAuthorizations"); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index 326c1fda..ae9a1652 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -81,6 +81,16 @@ namespace OpenIddict.EntityFramework /// protected DbSet Applications => Context.Set(); + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Tokens => Context.Set(); + /// /// Determines the number of applications that match the specified query. /// @@ -171,16 +181,48 @@ namespace OpenIddict.EntityFramework /// /// A that can be used to monitor the asynchronous operation. /// - public override Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } + Task ListAuthorizationsAsync() + { + return (from authorization in Authorizations.Include(authorization => authorization.Tokens) + where authorization.Application.Id.Equals(application.Id) + select authorization).ToArrayAsync(cancellationToken); + } + + Task ListTokensAsync() + { + return (from token in Tokens + where token.Application.Id.Equals(application.Id) + select token).ToArrayAsync(cancellationToken); + } + + // Remove all the authorizations associated with the application and + // the tokens attached to these implicit or explicit authorizations. + foreach (var authorization in await ListAuthorizationsAsync()) + { + foreach (var token in authorization.Tokens) + { + Tokens.Remove(token); + } + + Authorizations.Remove(authorization); + } + + // Remove all the tokens associated with the application. + foreach (var token in await ListTokensAsync()) + { + Tokens.Remove(token); + } + Applications.Remove(application); - return Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); } /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index d40f511f..f87e75f0 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -86,6 +86,11 @@ namespace OpenIddict.EntityFramework /// protected DbSet Authorizations => Context.Set(); + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Tokens => Context.Set(); + /// /// Determines the number of authorizations that match the specified query. /// @@ -178,16 +183,29 @@ namespace OpenIddict.EntityFramework /// /// A that can be used to monitor the asynchronous operation. /// - public override Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } + Task ListTokensAsync() + { + return (from token in Tokens + where token.Application.Id.Equals(authorization.Id) + select token).ToArrayAsync(cancellationToken); + } + + // Remove all the tokens associated with the application. + foreach (var token in await ListTokensAsync()) + { + Tokens.Remove(token); + } + Authorizations.Remove(authorization); - - return Context.SaveChangesAsync(cancellationToken); + + await Context.SaveChangesAsync(cancellationToken); } /// @@ -209,6 +227,38 @@ namespace OpenIddict.EntityFramework return Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); } + /// + /// Retrieves the optional application identifier 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 application identifier associated with the authorization. + /// + public override async Task GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization == null) + { + 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 (authorization.Application == null) + { + return ConvertIdentifierToString( + await Context.Entry(authorization) + .Reference(entry => entry.Application) + .Query() + .Select(application => application.Id) + .FirstOrDefaultAsync()); + } + + return ConvertIdentifierToString(authorization.Application.Id); + } + /// /// Executes the specified query. /// @@ -249,6 +299,47 @@ namespace OpenIddict.EntityFramework return ImmutableArray.Create(await query.Invoke(Authorizations).ToArrayAsync(cancellationToken)); } + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public override async Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (!string.IsNullOrEmpty(identifier)) + { + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (application == null) + { + throw new InvalidOperationException("The application associated with the authorization cannot be found."); + } + + authorization.Application = application; + } + + 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.Tokens.Any(t => t.Id.Equals(key))); + if (application != null) + { + application.Authorizations.Remove(authorization); + } + } + } + /// /// Updates an existing authorization. /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index ad4c2474..5d7c4f06 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -223,6 +223,38 @@ namespace OpenIddict.EntityFramework return Tokens.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); } + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// 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 application identifier associated with the token. + /// + public override async Task GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken) + { + if (token == null) + { + 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 (token.Application == null) + { + return ConvertIdentifierToString( + await Context.Entry(token) + .Reference(entry => entry.Application) + .Query() + .Select(application => application.Id) + .FirstOrDefaultAsync()); + } + + return ConvertIdentifierToString(token.Application.Id); + } + /// /// Executes the specified query. /// @@ -243,6 +275,38 @@ namespace OpenIddict.EntityFramework return query.Invoke(Tokens).SingleOrDefaultAsync(cancellationToken); } + /// + /// Retrieves the optional authorization identifier associated with a token. + /// + /// 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 authorization identifier associated with the token. + /// + public override async Task GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken) + { + if (token == null) + { + 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 (token.Authorization == null) + { + return ConvertIdentifierToString( + await Context.Entry(token) + .Reference(entry => entry.Authorization) + .Query() + .Select(authorization => authorization.Id) + .FirstOrDefaultAsync()); + } + + return ConvertIdentifierToString(token.Authorization.Id); + } + /// /// Executes the specified query. /// @@ -264,15 +328,15 @@ namespace OpenIddict.EntityFramework } /// - /// Sets the authorization associated with a token. + /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the client application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// - public override async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public override async Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { @@ -281,39 +345,39 @@ namespace OpenIddict.EntityFramework if (!string.IsNullOrEmpty(identifier)) { - var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); - if (authorization == null) + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (application == null) { - throw new InvalidOperationException("The authorization associated with the token cannot be found."); + throw new InvalidOperationException("The application associated with the token cannot be found."); } - token.Authorization = authorization; + token.Application = application; } 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) + // 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) { - authorization.Tokens.Remove(token); + application.Tokens.Remove(token); } } } /// - /// Sets the client application associated with a token. + /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// 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. /// - public override async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public override async Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { @@ -322,25 +386,25 @@ namespace OpenIddict.EntityFramework if (!string.IsNullOrEmpty(identifier)) { - var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); - if (application == null) + var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (authorization == null) { - throw new InvalidOperationException("The application associated with the token cannot be found."); + throw new InvalidOperationException("The authorization associated with the token cannot be found."); } - token.Application = application; + token.Authorization = authorization; } 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) + // 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) { - application.Tokens.Remove(token); + authorization.Tokens.Remove(token); } } } diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs index 379764ce..12d82a5b 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs @@ -11,6 +11,7 @@ using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.Core; using OpenIddict.EntityFrameworkCore; @@ -243,7 +244,8 @@ namespace Microsoft.Extensions.DependencyInjection entity.HasMany(application => application.Tokens) .WithOne(token => token.Application) .HasForeignKey("ApplicationId") - .IsRequired(required: false); + .IsRequired(required: false) + .OnDelete(DeleteBehavior.Cascade); entity.ToTable("OpenIddictApplications"); }); @@ -266,10 +268,11 @@ namespace Microsoft.Extensions.DependencyInjection entity.Property(authorization => authorization.Type) .IsRequired(); - entity.HasMany(application => application.Tokens) + entity.HasMany(authorization => authorization.Tokens) .WithOne(token => token.Authorization) .HasForeignKey("AuthorizationId") - .IsRequired(required: false); + .IsRequired(required: false) + .OnDelete(DeleteBehavior.Cascade); entity.ToTable("OpenIddictAuthorizations"); }); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index aab9aa0a..133704a6 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -81,6 +81,16 @@ namespace OpenIddict.EntityFrameworkCore /// protected DbSet Applications => Context.Set(); + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Tokens => Context.Set(); + /// /// Determines the number of applications that match the specified query. /// @@ -171,16 +181,48 @@ namespace OpenIddict.EntityFrameworkCore /// /// A that can be used to monitor the asynchronous operation. /// - public override Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } + Task ListAuthorizationsAsync() + { + return (from authorization in Authorizations.Include(authorization => authorization.Tokens) + where authorization.Application.Id.Equals(application.Id) + select authorization).ToArrayAsync(cancellationToken); + } + + Task ListTokensAsync() + { + return (from token in Tokens + where token.Application.Id.Equals(application.Id) + select token).ToArrayAsync(cancellationToken); + } + + // Remove all the authorizations associated with the application and + // the tokens attached to these implicit or explicit authorizations. + foreach (var authorization in await ListAuthorizationsAsync()) + { + foreach (var token in authorization.Tokens) + { + Context.Remove(token); + } + + Context.Remove(authorization); + } + + // Remove all the tokens associated with the application. + foreach (var token in await ListTokensAsync()) + { + Context.Remove(token); + } + Context.Remove(application); - return Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index a3d5e8af..eb7a22db 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -86,6 +86,11 @@ namespace OpenIddict.EntityFrameworkCore /// protected DbSet Authorizations => Context.Set(); + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Tokens => Context.Set(); + /// /// Determines the number of authorizations that match the specified query. /// @@ -180,16 +185,29 @@ namespace OpenIddict.EntityFrameworkCore /// /// A that can be used to monitor the asynchronous operation. /// - public override Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } + Task ListTokensAsync() + { + return (from token in Tokens + where token.Application.Id.Equals(authorization.Id) + select token).ToArrayAsync(cancellationToken); + } + + // Remove all the tokens associated with the application. + foreach (var token in await ListTokensAsync()) + { + Context.Remove(token); + } + Context.Remove(authorization); - return Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); } /// @@ -232,6 +250,49 @@ namespace OpenIddict.EntityFrameworkCore return ImmutableArray.Create(await query.Invoke(Authorizations).ToArrayAsync(cancellationToken)); } + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public override async Task SetApplicationIdAsync([NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (!string.IsNullOrEmpty(identifier)) + { + var key = ConvertIdentifierFromString(identifier); + + var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key), cancellationToken); + if (application == null) + { + throw new InvalidOperationException("The application associated with the authorization cannot be found."); + } + + authorization.Application = application; + } + + 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.Tokens.Any(t => t.Id.Equals(key))); + if (application != null) + { + application.Authorizations.Remove(authorization); + } + } + } + /// /// Updates an existing authorization. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 2254b6b0..46a97c04 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -249,15 +249,15 @@ namespace OpenIddict.EntityFrameworkCore } /// - /// Sets the authorization associated with a token. + /// Sets the application identifier associated with a token. /// /// The token. - /// The unique identifier associated with the authorization. + /// The unique identifier associated with the client application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// - public override async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public override async Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { @@ -268,39 +268,39 @@ namespace OpenIddict.EntityFrameworkCore { var key = ConvertIdentifierFromString(identifier); - var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Id.Equals(key)); - if (authorization == null) + var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key), cancellationToken); + if (application == null) { - throw new InvalidOperationException("The authorization associated with the token cannot be found."); + throw new InvalidOperationException("The application associated with the token cannot be found."); } - token.Authorization = authorization; + token.Application = application; } 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) + // 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) { - authorization.Tokens.Remove(token); + application.Tokens.Remove(token); } } } /// - /// Sets the client application associated with a token. + /// Sets the authorization identifier associated with a token. /// /// The token. - /// The unique identifier associated with the client application. + /// 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. /// - public override async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) + public override async Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) { if (token == null) { @@ -311,25 +311,25 @@ namespace OpenIddict.EntityFrameworkCore { var key = ConvertIdentifierFromString(identifier); - var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key)); - if (application == null) + var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Id.Equals(key), cancellationToken); + if (authorization == null) { - throw new InvalidOperationException("The application associated with the token cannot be found."); + throw new InvalidOperationException("The authorization associated with the token cannot be found."); } - token.Application = application; + token.Authorization = authorization; } 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) + // 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) { - application.Tokens.Remove(token); + authorization.Tokens.Remove(token); } } } diff --git a/src/OpenIddict/OpenIddictProvider.Helpers.cs b/src/OpenIddict/OpenIddictProvider.Helpers.cs index e3c70807..846cfb92 100644 --- a/src/OpenIddict/OpenIddictProvider.Helpers.cs +++ b/src/OpenIddict/OpenIddictProvider.Helpers.cs @@ -206,12 +206,11 @@ namespace OpenIddict ticket.SetTokenId(identifier); // Dynamically set the creation and expiration dates. - ticket.Properties.IssuedUtc = await tokens.GetCreationDateAsync(token, context.RequestAborted); - ticket.Properties.ExpiresUtc = await tokens.GetExpirationDateAsync(token, context.RequestAborted); + ticket.Properties.IssuedUtc = descriptor.CreationDate; + ticket.Properties.ExpiresUtc = descriptor.ExpirationDate; // Restore the authorization identifier using the identifier attached with the database entry. - ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, - await tokens.GetAuthorizationIdAsync(token, context.RequestAborted)); + ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, descriptor.AuthorizationId); if (!string.IsNullOrEmpty(result)) { diff --git a/src/OpenIddict/OpenIddictProvider.Signin.cs b/src/OpenIddict/OpenIddictProvider.Signin.cs index 72c38136..ef04be79 100644 --- a/src/OpenIddict/OpenIddictProvider.Signin.cs +++ b/src/OpenIddict/OpenIddictProvider.Signin.cs @@ -29,8 +29,8 @@ namespace OpenIddict // the OpenID Connect server middleware allows creating authentication tickets // that are completely disconnected from the original code or refresh token ticket. // This scenario is deliberately not supported in OpenIddict and all the tickets - // must be linked. To ensure the properties are preserved from an authorization code - // or a refresh token to the new ticket, they are manually restored if necessary. + // must be linked. To ensure the properties are flowed from the authorization code + // or the refresh token to the new ticket, they are manually restored if necessary. // Retrieve the original authentication ticket from the request properties. var ticket = context.Request.GetProperty(