diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs index e7fb0d4d..e94fc879 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs @@ -170,7 +170,7 @@ namespace OpenIddict.Infrastructure { // Note: filtering the username is not needed at this stage as OpenIddictController.Accept // and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // don't include the "email" scope if the username corresponds to the registed email address. - var identity = await services.Tokens.CreateIdentityAsync(user, context.Request.GetScopes()); + var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs index 5f2f8b55..11a5bf45 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs @@ -36,7 +36,7 @@ namespace OpenIddict.Infrastructure { // Note: the OpenID Connect server middleware allows returning a refresh token with grant_type=client_credentials, // though it's usually not recommended by the OAuth2 specification. To encourage developers to make a new // grant_type=client_credentials request instead of using refresh tokens, OpenIddict uses a stricter policy - // that rejects grant_type=client_credentials requests containg the 'offline_access' scope. + // that rejects grant_type=client_credentials requests containing the 'offline_access' scope. // See https://tools.ietf.org/html/rfc6749#section-4.4.3 for more information. if (context.Request.IsClientCredentialsGrantType() && context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess)) { @@ -182,7 +182,7 @@ namespace OpenIddict.Infrastructure { // Note: the "scopes" property stored in context.AuthenticationTicket is automatically // updated by ASOS when the client application requests a restricted scopes collection. - var identity = await services.Tokens.CreateIdentityAsync(user, context.Ticket.GetScopes()); + var identity = await services.Users.CreateIdentityAsync(user, context.Ticket.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity but @@ -275,7 +275,7 @@ namespace OpenIddict.Infrastructure { } } - var identity = await services.Tokens.CreateIdentityAsync(user, context.Request.GetScopes()); + var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs index c81909f9..f321365a 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs @@ -5,11 +5,14 @@ */ using System; +using System.Diagnostics; +using System.Security.Claims; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIddict.Infrastructure { public partial class OpenIddictProvider : OpenIdConnectServerProvider @@ -17,8 +20,40 @@ namespace OpenIddict.Infrastructure { public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { var services = context.HttpContext.RequestServices.GetRequiredService>(); - // Persist a new token entry in the database. - var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken); + Debug.Assert(context.Request.RequestType == OpenIdConnectRequestType.TokenRequest, + "The request should be a token request."); + + Debug.Assert(!context.Request.IsClientCredentialsGrantType(), + "A refresh token should not be issued when using grant_type=client_credentials."); + + var user = await services.Users.FindByIdAsync(context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier)); + if (user == null) { + throw new InvalidOperationException("The user cannot be retrieved from the database."); + } + + string identifier; + + // If the client application sending the token request is known, + // ensure the token is attached to the corresponding client entity. + if (!string.IsNullOrEmpty(context.Request.ClientId)) { + var application = await services.Applications.FindByIdAsync(context.Request.ClientId); + if (application == null) { + throw new InvalidOperationException("The application cannot be retrieved from the database."); + } + + // Persist a new token entry in the database and attach it + // to the user and the client application it is issued to. + identifier = await services.Users.CreateTokenAsync(user, context.Request.ClientId, + OpenIdConnectConstants.TokenTypeHints.RefreshToken); + } + + else { + // Persist a new token entry in the database + // and attach it to the user it corresponds to. + identifier = await services.Users.CreateTokenAsync(user, + OpenIdConnectConstants.TokenTypeHints.RefreshToken); + } + if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs index 9a5b1077..40d69379 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs @@ -56,14 +56,13 @@ namespace OpenIddict.Infrastructure { public virtual SignInManager SignIn => Services.GetRequiredService>(); /// - /// Gets the . + /// Gets the . /// - public virtual OpenIddictTokenManager Tokens => - Services.GetRequiredService>(); + public virtual OpenIddictTokenManager Tokens => Services.GetRequiredService>(); /// - /// Gets the . + /// Gets the . /// - public virtual UserManager Users => Services.GetRequiredService>(); + public virtual OpenIddictUserManager Users => Services.GetRequiredService>(); } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 9056479f..180c6855 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -5,6 +5,7 @@ */ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using CryptoHelper; @@ -129,6 +130,22 @@ namespace OpenIddict { return Store.GetDisplayNameAsync(application, CancellationToken); } + /// + /// Retrieves the token identifiers associated with an application. + /// + /// The application. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens associated with the application. + /// + public virtual Task> GetTokensAsync(TApplication application) { + if (application == null) { + throw new ArgumentNullException(nameof(application)); + } + + return Store.GetTokensAsync(application, CancellationToken); + } + /// /// Validates the redirect_uri associated with an application. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index ec744bea..75288bf8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -19,7 +19,7 @@ namespace OpenIddict { public class OpenIddictScopeManager where TScope : class { public OpenIddictScopeManager( [NotNull] IServiceProvider services, - [NotNull] IOpenIddictAuthorizationStore store, + [NotNull] IOpenIddictScopeStore store, [NotNull] ILogger> logger) { Context = services?.GetRequiredService()?.HttpContext; Logger = logger; @@ -44,6 +44,6 @@ namespace OpenIddict { /// /// Gets the store associated with the current manager. /// - protected IOpenIddictAuthorizationStore Store { get; } + protected IOpenIddictScopeStore Store { get; } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index a4cc135a..696a8602 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -5,17 +5,11 @@ */ using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -25,19 +19,16 @@ namespace OpenIddict { /// Provides methods allowing to manage the tokens stored in the store. /// /// The type of the Token entity. - /// The type of the User entity. - public class OpenIddictTokenManager where TToken : class where TUser : class { + public class OpenIddictTokenManager where TToken : class { public OpenIddictTokenManager( [NotNull] IServiceProvider services, [NotNull] IOpenIddictTokenStore store, - [NotNull] UserManager users, [NotNull] IOptions options, - [NotNull] ILogger> logger) { + [NotNull] ILogger> logger) { Context = services?.GetRequiredService()?.HttpContext; Logger = logger; Options = options.Value; Store = store; - Users = users; } /// @@ -66,87 +57,7 @@ namespace OpenIddict { protected IOpenIddictTokenStore Store { get; } /// - /// Gets the user manager. - /// - protected UserManager Users { get; } - - /// - /// Creates a new used to create new tokens. - /// - /// The user corresponding to the identity. - /// The scopes granted by the resource owner. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the corresponding to the user. - /// - public virtual async Task CreateIdentityAsync(TUser user, IEnumerable scopes) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (scopes == null) { - throw new ArgumentNullException(nameof(scopes)); - } - - var identity = new ClaimsIdentity( - OpenIdConnectServerDefaults.AuthenticationScheme, - Options.ClaimsIdentity.UserNameClaimType, - Options.ClaimsIdentity.RoleClaimType); - - // Note: the name identifier is always included in both identity and - // access tokens, even if an explicit destination is not specified. - identity.AddClaim(ClaimTypes.NameIdentifier, await Users.GetUserIdAsync(user)); - - // Resolve the email address associated with the user if the underlying store supports it. - var email = Users.SupportsUserEmail ? await Users.GetEmailAsync(user) : null; - - // Only add the name claim if the "profile" scope was granted. - if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { - var username = await Users.GetUserNameAsync(user); - - // Throw an exception if the username corresponds to the registered - // email address and if the "email" scope has not been requested. - if (!scopes.Contains(OpenIdConnectConstants.Scopes.Email) && - !string.IsNullOrEmpty(email) && - string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) { - throw new InvalidOperationException("The 'email' scope is required."); - } - - identity.AddClaim(ClaimTypes.Name, username, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - - // Only add the email address if the "email" scope was granted. - if (!string.IsNullOrEmpty(email) && scopes.Contains(OpenIdConnectConstants.Scopes.Email)) { - identity.AddClaim(ClaimTypes.Email, email, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - - if (Users.SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { - foreach (var role in await Users.GetRolesAsync(user)) { - identity.AddClaim(identity.RoleClaimType, role, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - } - - if (Users.SupportsUserSecurityStamp) { - var stamp = await Users.GetSecurityStampAsync(user); - - if (!string.IsNullOrEmpty(stamp)) { - identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, stamp, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - } - - return identity; - } - - /// - /// Creates a new token, defined by a unique identifier and a token type. + /// Creates a new token, which is not associated with a particular user or client. /// /// The token type. /// @@ -155,7 +66,7 @@ namespace OpenIddict { /// public virtual Task CreateAsync(string type) { if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty", nameof(type)); + throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); } return Store.CreateAsync(type, CancellationToken); @@ -170,10 +81,6 @@ namespace OpenIddict { /// whose result returns the token corresponding to the unique identifier. /// public virtual Task FindByIdAsync(string identifier) { - if (string.IsNullOrEmpty(identifier)) { - throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier)); - } - return Store.FindByIdAsync(identifier, CancellationToken); } diff --git a/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs b/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs new file mode 100644 index 00000000..785f5389 --- /dev/null +++ b/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs @@ -0,0 +1,201 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using AspNet.Security.OpenIdConnect.Extensions; +using AspNet.Security.OpenIdConnect.Server; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace OpenIddict { + /// + /// Provides methods allowing to manage the users stored in the store. + /// + /// The type of the User entity. + public class OpenIddictUserManager : UserManager where TUser : class { + public OpenIddictUserManager( + [NotNull] IServiceProvider services, + [NotNull] IOpenIddictUserStore store, + [NotNull] IOptions options, + [NotNull] ILogger> logger, + [NotNull] IPasswordHasher hasher, + [NotNull] IEnumerable> userValidators, + [NotNull] IEnumerable> passwordValidators, + [NotNull] ILookupNormalizer keyNormalizer, + [NotNull] IdentityErrorDescriber errors) + : base(store, options, hasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { + Context = services?.GetRequiredService()?.HttpContext; + Logger = logger; + Options = options.Value; + Store = store; + } + + /// + /// Gets the cancellation token used to abort async operations. + /// + protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; + + /// + /// Gets the HTTP context associated with the current manager. + /// + protected HttpContext Context { get; } + + /// + /// Gets the identity options. + /// + protected IdentityOptions Options { get; } + + /// + /// Gets the store associated with the current manager. + /// + protected new IOpenIddictUserStore Store { get; } + + /// + /// Creates a new used to create new tokens. + /// + /// The user corresponding to the identity. + /// The scopes granted by the resource owner. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the corresponding to the user. + /// + public virtual async Task CreateIdentityAsync(TUser user, IEnumerable scopes) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (scopes == null) { + throw new ArgumentNullException(nameof(scopes)); + } + + var identity = new ClaimsIdentity( + OpenIdConnectServerDefaults.AuthenticationScheme, + Options.ClaimsIdentity.UserNameClaimType, + Options.ClaimsIdentity.RoleClaimType); + + // Note: the name identifier is always included in both identity and + // access tokens, even if an explicit destination is not specified. + identity.AddClaim(ClaimTypes.NameIdentifier, await GetUserIdAsync(user)); + + // Resolve the email address associated with the user if the underlying store supports it. + var email = SupportsUserEmail ? await GetEmailAsync(user) : null; + + // Only add the name claim if the "profile" scope was granted. + if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { + var username = await GetUserNameAsync(user); + + // Throw an exception if the username corresponds to the registered + // email address and if the "email" scope has not been requested. + if (!scopes.Contains(OpenIdConnectConstants.Scopes.Email) && + !string.IsNullOrEmpty(email) && + string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) { + throw new InvalidOperationException("The 'email' scope is required."); + } + + identity.AddClaim(ClaimTypes.Name, username, + OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + + // Only add the email address if the "email" scope was granted. + if (!string.IsNullOrEmpty(email) && scopes.Contains(OpenIdConnectConstants.Scopes.Email)) { + identity.AddClaim(ClaimTypes.Email, email, + OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + + if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { + foreach (var role in await GetRolesAsync(user)) { + identity.AddClaim(identity.RoleClaimType, role, + OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + } + + if (SupportsUserSecurityStamp) { + var stamp = await GetSecurityStampAsync(user); + + if (!string.IsNullOrEmpty(stamp)) { + identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, stamp, + OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + } + + return identity; + } + + /// + /// Creates a new token associated with the given user. + /// + /// The user. + /// The token type. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual Task CreateTokenAsync(TUser user, string type) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(type)) { + throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); + } + + return Store.CreateTokenAsync(user, type, CancellationToken); + } + + /// + /// Creates a new token associated with the given user and + /// attached to the tokens issued to the specified client. + /// + /// The user. + /// The application. + /// The token type. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual Task CreateTokenAsync(TUser user, string client, string type) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(type)) { + throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); + } + + return Store.CreateTokenAsync(user, client, type, CancellationToken); + } + + /// + /// Retrieves the token identifiers associated with a user. + /// + /// The user. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens associated with the user. + /// + public virtual Task> GetTokensAsync(TUser user) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + return Store.GetTokensAsync(user, CancellationToken); + } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index e07d1651..ed5286c7 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -40,6 +40,12 @@ namespace Microsoft.AspNetCore.Builder { [EditorBrowsable(EditorBrowsableState.Never)] public Type AuthorizationType { get; set; } + /// + /// Gets or sets the type corresponding to the Role entity. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Type RoleType { get; set; } + /// /// Gets or sets the type corresponding to the Scope entity. /// @@ -182,7 +188,7 @@ namespace Microsoft.AspNetCore.Builder { /// The type of the custom manager. /// The . public virtual OpenIddictBuilder AddTokenManager() { - var contract = typeof(OpenIddictTokenManager<,>).MakeGenericType(TokenType, UserType); + var contract = typeof(OpenIddictTokenManager<>).MakeGenericType(TokenType); if (!contract.IsAssignableFrom(typeof(TManager))) { throw new InvalidOperationException("Custom managers must be derived from OpenIddictTokenManager."); } diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 6dde32dc..d90b9f5c 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Builder { /// When using this method, custom stores must be manually registered. /// /// The type of the User entity. + /// The type of the Role entity. /// The type of the Application entity. /// The type of the Authorization entity. /// The type of the Scope entity. @@ -32,9 +33,10 @@ namespace Microsoft.AspNetCore.Builder { /// consider adding the MVC module or creating your own authorization controller. /// /// The . - public static OpenIddictBuilder AddOpenIddict( + public static OpenIddictBuilder AddOpenIddict( [NotNull] this IServiceCollection services) where TUser : class + where TRole : class where TApplication : class where TAuthorization : class where TScope : class @@ -46,6 +48,7 @@ namespace Microsoft.AspNetCore.Builder { var builder = new OpenIddictBuilder(services) { ApplicationType = typeof(TApplication), AuthorizationType = typeof(TAuthorization), + RoleType = typeof(TRole), ScopeType = typeof(TScope), TokenType = typeof(TToken), UserType = typeof(TUser) @@ -64,7 +67,8 @@ namespace Microsoft.AspNetCore.Builder { builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); + builder.Services.TryAddScoped>(); + builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); return builder; diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index b900e7f0..bb3fad7e 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -68,6 +69,17 @@ namespace OpenIddict { /// Task GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken); + /// + /// Retrieves the hashed secret associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the hashed secret associated with the application. + /// + Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken); + /// /// Retrieves the callback address associated with an application. /// @@ -80,14 +92,14 @@ namespace OpenIddict { Task GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken); /// - /// Retrieves the hashed secret associated with an application. + /// Retrieves the token identifiers associated with an application. /// /// The application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, - /// whose result returns the hashed secret associated with the application. + /// whose result returns the tokens associated with the application. /// - Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken); + Task> GetTokensAsync(TApplication application, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index 6450b91f..f7f5868d 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -14,7 +14,7 @@ namespace OpenIddict { /// The type of the Token entity. public interface IOpenIddictTokenStore where TToken : class { /// - /// Creates a new token, defined by a unique identifier and a token type. + /// Creates a new token, which is not associated with a particular user or client. /// /// The token type. /// The that can be used to abort the operation. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs new file mode 100644 index 00000000..39fcd333 --- /dev/null +++ b/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs @@ -0,0 +1,55 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; + +namespace OpenIddict { + /// + /// Provides methods allowing to manage the users stored in a database. + /// + /// The type of the User entity. + public interface IOpenIddictUserStore : IUserStore where TUser : class { + /// + /// Creates a new token associated with the given user. + /// + /// The user associated with the token. + /// The token type. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + Task CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken); + + /// + /// Creates a new token associated with the given user and + /// attached to the tokens issued to the specified client. + /// + /// The user. + /// The application. + /// The token type. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + Task CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken); + + /// + /// Retrieves the token identifiers associated with a user. + /// + /// The user. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens associated with the user. + /// + Task> GetTokensAsync(TUser user, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs index f2e798c7..5bf26a12 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs +++ b/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs @@ -5,12 +5,13 @@ */ using System; +using System.Collections.Generic; namespace OpenIddict { /// /// Represents an OpenIddict application. /// - public class OpenIddictApplication : OpenIddictApplication { + public class OpenIddictApplication : OpenIddictApplication { public OpenIddictApplication() { // Generate a new string identifier. Id = Guid.NewGuid().ToString(); @@ -20,7 +21,13 @@ namespace OpenIddict { /// /// Represents an OpenIddict application. /// - public class OpenIddictApplication where TKey : IEquatable { + public class OpenIddictApplication : OpenIddictApplication> + where TKey : IEquatable { } + + /// + /// Represents an OpenIddict application. + /// + public class OpenIddictApplication where TKey : IEquatable { /// /// Gets or sets the display name /// associated with the current application. @@ -51,6 +58,11 @@ namespace OpenIddict { /// public virtual string Secret { get; set; } + /// + /// Gets the list of the tokens associated with this application. + /// + public virtual IList Tokens { get; } = new List(); + /// /// Gets or sets the application type /// associated with the current application. diff --git a/src/OpenIddict.EntityFramework/OpenIddictContext.cs b/src/OpenIddict.EntityFramework/OpenIddictContext.cs index 6e192c15..7a6ea6c4 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictContext.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictContext.cs @@ -107,7 +107,7 @@ namespace OpenIddict { public class OpenIddictContext : IdentityDbContext where TUser : OpenIddictUser where TRole : IdentityRole - where TApplication : OpenIddictApplication + where TApplication : OpenIddictApplication where TAuthorization : OpenIddictAuthorization where TScope : OpenIddictScope where TToken : OpenIddictToken @@ -158,6 +158,11 @@ namespace OpenIddict { builder.Entity(entity => { entity.HasKey(application => application.Id); + entity.HasMany(application => application.Tokens) + .WithOne() + .HasForeignKey("ApplicationId") + .IsRequired(required: false); + entity.ToTable("OpenIddictApplications"); }); diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index c79e4518..0b65a4dc 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -37,14 +37,16 @@ namespace Microsoft.AspNetCore.Builder { Debug.Assert(builder.ApplicationType != null && builder.AuthorizationType != null && + builder.RoleType != null && builder.ScopeType != null && builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null."); // Register the application store in the DI container. builder.Services.TryAddScoped( typeof(IOpenIddictApplicationStore<>).MakeGenericType(builder.ApplicationType), - typeof(OpenIddictApplicationStore<,,>).MakeGenericType( + typeof(OpenIddictApplicationStore<,,,>).MakeGenericType( /* TApplication: */ builder.ApplicationType, + /* TToken: */ builder.TokenType, /* TContext: */ typeof(TContext), /* TKey: */ typeof(TKey))); @@ -68,7 +70,21 @@ namespace Microsoft.AspNetCore.Builder { // Register the token store in the DI container. builder.Services.TryAddScoped( typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType), - typeof(OpenIddictTokenStore<,,>).MakeGenericType( + typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( + /* TToken: */ builder.TokenType, + /* TAuthorization: */ builder.AuthorizationType, + /* TUser: */ builder.UserType, + /* TContext: */ typeof(TContext), + /* TKey: */ typeof(TKey))); + + // Register the token store in the DI container. + builder.Services.TryAddScoped( + typeof(IOpenIddictUserStore<>).MakeGenericType(builder.UserType), + typeof(OpenIddictUserStore<,,,,,,>).MakeGenericType( + /* TUser: */ builder.UserType, + /* TApplication: */ builder.ApplicationType, + /* TAuthorization: */ builder.AuthorizationType, + /* TRole: */ builder.RoleType, /* TToken: */ builder.TokenType, /* TContext: */ typeof(TContext), /* TKey: */ typeof(TKey))); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index b0e0b80b..b37edb96 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -5,7 +5,9 @@ */ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -15,10 +17,12 @@ namespace OpenIddict { /// Provides methods allowing to manage the applications stored in a database. /// /// The type of the Application entity. + /// The type of the Token entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. - public class OpenIddictApplicationStore : IOpenIddictApplicationStore - where TApplication : OpenIddictApplication + public class OpenIddictApplicationStore : IOpenIddictApplicationStore + where TApplication : OpenIddictApplication + where TToken : OpenIddictToken, new() where TContext : DbContext where TKey : IEquatable { public OpenIddictApplicationStore(TContext context) { @@ -129,6 +133,23 @@ namespace OpenIddict { return Task.FromResult(application.DisplayName); } + /// + /// Retrieves the hashed secret associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the hashed secret associated with the application. + /// + public virtual Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) { + if (application == null) { + throw new ArgumentNullException(nameof(application)); + } + + return Task.FromResult(application.Secret); + } + /// /// Retrieves the callback address associated with an application. /// @@ -147,20 +168,37 @@ namespace OpenIddict { } /// - /// Retrieves the hashed secret associated with an application. + /// Retrieves the token identifiers associated with an application. /// /// The application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, - /// whose result returns the hashed secret associated with the application. + /// whose result returns the tokens associated with the application. /// - public virtual Task GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) { + public virtual async Task> GetTokensAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - return Task.FromResult(application.Secret); + // Ensure that the key type can be serialized. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); + if (!converter.CanConvertTo(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); + } + + var query = from entity in Applications + where entity.Id.Equals(application.Id) + from token in entity.Tokens + select token.Id; + + var tokens = new List(); + + foreach (var identifier in await query.ToArrayAsync()) { + tokens.Add(converter.ConvertToInvariantString(identifier)); + } + + return tokens; } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 26f62789..ce761ca2 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -15,10 +15,14 @@ namespace OpenIddict { /// Provides methods allowing to manage the tokens stored in a database. /// /// The type of the Token entity. + /// The type of the Authorization entity. + /// The type of the User entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. - public class OpenIddictTokenStore : IOpenIddictTokenStore + public class OpenIddictTokenStore : IOpenIddictTokenStore where TToken : OpenIddictToken, new() + where TAuthorization : OpenIddictAuthorization + where TUser : OpenIddictUser where TContext : DbContext where TKey : IEquatable { public OpenIddictTokenStore(TContext context) { @@ -36,7 +40,7 @@ namespace OpenIddict { protected DbSet Tokens => Context.Set(); /// - /// Creates a new token, defined by a unique identifier and a token type. + /// Creates a new token, which is not associated with a particular user or client. /// /// The token type. /// The that can be used to abort the operation. @@ -45,6 +49,10 @@ namespace OpenIddict { /// whose result returns the unique identifier associated with the token. /// public virtual async Task CreateAsync(string type, CancellationToken cancellationToken) { + if (string.IsNullOrEmpty(type)) { + throw new ArgumentException("The token type cannot be null or empty."); + } + // Ensure that the key type can be serialized. var converter = TypeDescriptor.GetConverter(typeof(TKey)); if (!converter.CanConvertTo(typeof(string))) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs new file mode 100644 index 00000000..1d649954 --- /dev/null +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs @@ -0,0 +1,164 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace OpenIddict { + /// + /// Provides methods allowing to manage the users stored in a database. + /// + /// The type of the User entity. + /// The type of the Application entity. + /// The type of the Authorization entity. + /// The type of the Role entity. + /// The type of the Token entity. + /// The type of the Entity Framework database context. + /// The type of the entity primary keys. + public class OpenIddictUserStore : + UserStore, IOpenIddictUserStore + where TUser : OpenIddictUser, new() + where TApplication : OpenIddictApplication + where TAuthorization : OpenIddictAuthorization + where TRole : IdentityRole + where TToken : OpenIddictToken, new() + where TContext : DbContext + where TKey : IEquatable { + public OpenIddictUserStore(TContext context) + : base(context) { } + + /// + /// Gets the database set corresponding to the entity. + /// + protected DbSet Applications => Context.Set(); + + /// + /// Creates a new token associated with the given user and defined by a unique identifier and a token type. + /// + /// The user associated with the token. + /// The token type. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual async Task CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(type)) { + throw new ArgumentException("The token type cannot be null or empty."); + } + + // Ensure that the key type can be serialized. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); + if (!converter.CanConvertTo(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); + } + + var token = new TToken { Type = type }; + user.Tokens.Add(token); + + Context.Update(user); + + await Context.SaveChangesAsync(cancellationToken); + + return converter.ConvertToInvariantString(token.Id); + } + + /// + /// Creates a new token associated with the given user and + /// attached to the tokens issued to the specified client. + /// + /// The user. + /// The application. + /// The token type. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual async Task CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(client)) { + throw new ArgumentException("The client identifier cannot be null or empty."); + } + + if (string.IsNullOrEmpty(type)) { + throw new ArgumentException("The token type cannot be null or empty."); + } + + // Ensure that the key type can be serialized. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); + if (!converter.CanConvertTo(typeof(string)) || !converter.CanConvertFrom(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); + } + + var key = (TKey) converter.ConvertFromInvariantString(client); + + var application = await Applications.FirstOrDefaultAsync(entity => entity.Id.Equals(key), cancellationToken); + if (application == null) { + throw new InvalidOperationException("The application cannot be found in the database."); + } + + var token = new TToken { Type = type }; + + application.Tokens.Add(token); + user.Tokens.Add(token); + + Context.Update(application); + Context.Update(user); + + await Context.SaveChangesAsync(cancellationToken); + + return converter.ConvertToInvariantString(token.Id); + } + + /// + /// Retrieves the token identifiers associated with a user. + /// + /// The user. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens associated with the user. + /// + public virtual async Task> GetTokensAsync(TUser user, CancellationToken cancellationToken) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + // Ensure that the key type can be serialized. + var converter = TypeDescriptor.GetConverter(typeof(TKey)); + if (!converter.CanConvertTo(typeof(string))) { + throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); + } + + var query = from entity in Users + where entity.Id.Equals(user.Id) + from token in entity.Tokens + select token.Id; + + var tokens = new List(); + + foreach (var identifier in await query.ToArrayAsync()) { + tokens.Add(converter.ConvertToInvariantString(identifier)); + } + + return tokens; + } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Mvc/OpenIddictController.cs b/src/OpenIddict.Mvc/OpenIddictController.cs index 9ede6c57..fa634a25 100644 --- a/src/OpenIddict.Mvc/OpenIddictController.cs +++ b/src/OpenIddict.Mvc/OpenIddictController.cs @@ -70,9 +70,8 @@ namespace OpenIddict.Mvc { [Authorize, HttpPost, ValidateAntiForgeryToken] public virtual async Task Accept( - [FromServices] UserManager users, + [FromServices] OpenIddictUserManager users, [FromServices] OpenIddictApplicationManager applications, - [FromServices] OpenIddictTokenManager tokens, [FromServices] IOptions options) { var response = HttpContext.GetOpenIdConnectResponse(); if (response != null) { @@ -98,7 +97,7 @@ namespace OpenIddict.Mvc { // Create a new ClaimsIdentity containing the claims that // will be used to create an id_token, a token or a code. - var identity = await tokens.CreateIdentityAsync(user, request.GetScopes()); + var identity = await users.CreateIdentityAsync(user, request.GetScopes()); Debug.Assert(identity != null); var application = await applications.FindByIdAsync(request.ClientId); diff --git a/src/OpenIddict.Mvc/OpenIddictExtensions.cs b/src/OpenIddict.Mvc/OpenIddictExtensions.cs index ef4aa170..e885fa24 100644 --- a/src/OpenIddict.Mvc/OpenIddictExtensions.cs +++ b/src/OpenIddict.Mvc/OpenIddictExtensions.cs @@ -119,37 +119,39 @@ namespace Microsoft.AspNetCore.Builder { return container.GetRequiredService(typeof(OpenIddictAuthorizationManager<>).MakeGenericType(builder.AuthorizationType)); }); - // Register the sign-in manager in the isolated container. - services.AddScoped(typeof(SignInManager<>).MakeGenericType(builder.UserType), provider => { + // Register the token manager in the isolated container. + services.AddScoped(typeof(OpenIddictTokenManager<>).MakeGenericType(builder.TokenType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null, "The parent DI container cannot be resolved from the HTTP context."); - // Resolve the sign-in manager from the parent container. - return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(builder.UserType)); + // Resolve the token manager from the parent container. + return container.GetRequiredService(typeof(OpenIddictTokenManager<>).MakeGenericType(builder.TokenType)); }); - // Register the token manager in the isolated container. - services.AddScoped(typeof(OpenIddictTokenManager<,>).MakeGenericType( - /* TToken: */ builder.TokenType, - /* TUser: */ builder.UserType), provider => { + // Register the user manager in the isolated container. + services.AddScoped(typeof(OpenIddictUserManager<>).MakeGenericType(builder.UserType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null, "The parent DI container cannot be resolved from the HTTP context."); - // Resolve the token manager from the parent container. - return container.GetRequiredService(typeof(OpenIddictTokenManager<,>).MakeGenericType( - /* TToken: */ builder.TokenType, /* TUser: */ builder.UserType)); + // Resolve the user manager from the parent container. + return container.GetRequiredService(typeof(OpenIddictUserManager<>).MakeGenericType(builder.UserType)); }); - // Register the user manager in the isolated container. - services.AddScoped(typeof(UserManager<>).MakeGenericType(builder.UserType), provider => { + // Register the sign-in manager in the isolated container. + services.AddScoped(typeof(SignInManager<>).MakeGenericType(builder.UserType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null, "The parent DI container cannot be resolved from the HTTP context."); - // Resolve the user manager from the parent container. - return container.GetRequiredService(typeof(UserManager<>).MakeGenericType(builder.UserType)); + // Resolve the sign-in manager from the parent container. + return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(builder.UserType)); + }); + + // Register the user manager in the isolated container. + services.AddScoped(typeof(UserManager<>).MakeGenericType(builder.UserType), provider => { + return provider.GetRequiredService(typeof(OpenIddictUserManager<>).MakeGenericType(builder.UserType)); }); // Register the options in the isolated container. diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 96c1e08a..0055cc8d 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -6,6 +6,7 @@ using System; using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OpenIddict; @@ -55,10 +56,10 @@ namespace Microsoft.AspNetCore.Builder { throw new ArgumentNullException(nameof(services)); } - return services.AddOpenIddict(); + return services.AddOpenIddict(); } /// @@ -66,6 +67,36 @@ namespace Microsoft.AspNetCore.Builder { /// including the Entity Framework stores and the specified entities. /// /// The type of the User entity. + /// The type of the Role entity. + /// The type of the Entity Framework database context. + /// The services collection. + /// + /// Note: the core services include native support for the non-interactive flows + /// (resource owner password credentials, client credentials, refresh token). + /// To support interactive flows like authorization code or implicit/hybrid, + /// consider adding the MVC module or creating your own authorization controller. + /// + /// The . + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + where TUser : OpenIddictUser + where TRole : IdentityRole + where TContext : DbContext { + if (services == null) { + throw new ArgumentNullException(nameof(services)); + } + + return services.AddOpenIddict(); + } + + /// + /// Registers the default OpenIddict services in the DI container, + /// including the Entity Framework stores and the specified entities. + /// + /// The type of the User entity. + /// The type of the Role entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. /// The services collection. @@ -76,18 +107,19 @@ namespace Microsoft.AspNetCore.Builder { /// consider adding the MVC module or creating your own authorization controller. /// /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) where TUser : OpenIddictUser + where TRole : IdentityRole where TContext : DbContext where TKey : IEquatable { if (services == null) { throw new ArgumentNullException(nameof(services)); } - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TContext, TKey>(); + return services.AddOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken, TContext, TKey>(); } /// @@ -95,6 +127,7 @@ namespace Microsoft.AspNetCore.Builder { /// including the Entity Framework stores and the specified entities. /// /// The type of the User entity. + /// The type of the Role entity. /// The type of the Application entity. /// The type of the Authorization entity. /// The type of the Scope entity. @@ -109,10 +142,11 @@ namespace Microsoft.AspNetCore.Builder { /// consider adding the MVC module or creating your own authorization controller. /// /// The . - public static OpenIddictBuilder AddOpenIddict( + public static OpenIddictBuilder AddOpenIddict( [NotNull] this IServiceCollection services) where TUser : OpenIddictUser - where TApplication : OpenIddictApplication + where TRole : IdentityRole + where TApplication : OpenIddictApplication where TAuthorization : OpenIddictAuthorization where TScope : OpenIddictScope where TToken : OpenIddictToken @@ -123,7 +157,7 @@ namespace Microsoft.AspNetCore.Builder { } // Register the OpenIddict core services and the default EntityFramework stores. - return services.AddOpenIddict() + return services.AddOpenIddict() .AddEntityFramework(); } }