Browse Source

Introduce OpenIddictUserManager and add OpenIddictToken/OpenIddictUser/OpenIddictApplication relationships support

pull/135/head
Kévin Chalet 10 years ago
parent
commit
1bde9c2cf5
  1. 2
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  2. 6
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  3. 39
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs
  4. 9
      src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs
  5. 17
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  6. 4
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  7. 101
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  8. 201
      src/OpenIddict.Core/Managers/OpenIddictUserManager.cs
  9. 8
      src/OpenIddict.Core/OpenIddictBuilder.cs
  10. 8
      src/OpenIddict.Core/OpenIddictExtensions.cs
  11. 18
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  12. 2
      src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
  13. 55
      src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs
  14. 16
      src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs
  15. 7
      src/OpenIddict.EntityFramework/OpenIddictContext.cs
  16. 20
      src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
  17. 50
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  18. 12
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  19. 164
      src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs
  20. 5
      src/OpenIddict.Mvc/OpenIddictController.cs
  21. 32
      src/OpenIddict.Mvc/OpenIddictExtensions.cs
  22. 58
      src/OpenIddict/OpenIddictExtensions.cs

2
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.

6
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.

39
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<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -17,8 +20,40 @@ namespace OpenIddict.Infrastructure {
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// 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.");
}

9
src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs

@ -56,14 +56,13 @@ namespace OpenIddict.Infrastructure {
public virtual SignInManager<TUser> SignIn => Services.GetRequiredService<SignInManager<TUser>>();
/// <summary>
/// Gets the <see cref="OpenIddictTokenManager{TToken, TUser}"/>.
/// Gets the <see cref="OpenIddictTokenManager{TToken}"/>.
/// </summary>
public virtual OpenIddictTokenManager<TToken, TUser> Tokens =>
Services.GetRequiredService<OpenIddictTokenManager<TToken, TUser>>();
public virtual OpenIddictTokenManager<TToken> Tokens => Services.GetRequiredService<OpenIddictTokenManager<TToken>>();
/// <summary>
/// Gets the <see cref="UserManager{TUser}"/>.
/// Gets the <see cref="OpenIddictUserManager{TUser}"/>.
/// </summary>
public virtual UserManager<TUser> Users => Services.GetRequiredService<UserManager<TUser>>();
public virtual OpenIddictUserManager<TUser> Users => Services.GetRequiredService<OpenIddictUserManager<TUser>>();
}
}

17
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);
}
/// <summary>
/// Retrieves the token identifiers associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens associated with the application.
/// </returns>
public virtual Task<IEnumerable<string>> GetTokensAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetTokensAsync(application, CancellationToken);
}
/// <summary>
/// Validates the redirect_uri associated with an application.
/// </summary>

4
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -19,7 +19,7 @@ namespace OpenIddict {
public class OpenIddictScopeManager<TScope> where TScope : class {
public OpenIddictScopeManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictAuthorizationStore<TScope> store,
[NotNull] IOpenIddictScopeStore<TScope> store,
[NotNull] ILogger<OpenIddictAuthorizationManager<TScope>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
@ -44,6 +44,6 @@ namespace OpenIddict {
/// <summary>
/// Gets the store associated with the current manager.
/// </summary>
protected IOpenIddictAuthorizationStore<TScope> Store { get; }
protected IOpenIddictScopeStore<TScope> Store { get; }
}
}

101
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.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
public class OpenIddictTokenManager<TToken, TUser> where TToken : class where TUser : class {
public class OpenIddictTokenManager<TToken> where TToken : class {
public OpenIddictTokenManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictTokenStore<TToken> store,
[NotNull] UserManager<TUser> users,
[NotNull] IOptions<IdentityOptions> options,
[NotNull] ILogger<OpenIddictTokenManager<TToken, TUser>> logger) {
[NotNull] ILogger<OpenIddictTokenManager<TToken>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Options = options.Value;
Store = store;
Users = users;
}
/// <summary>
@ -66,87 +57,7 @@ namespace OpenIddict {
protected IOpenIddictTokenStore<TToken> Store { get; }
/// <summary>
/// Gets the user manager.
/// </summary>
protected UserManager<TUser> Users { get; }
/// <summary>
/// Creates a new <see cref="ClaimsIdentity"/> used to create new tokens.
/// </summary>
/// <param name="user">The user corresponding to the identity.</param>
/// <param name="scopes">The scopes granted by the resource owner.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the <see cref="ClaimsIdentity"/> corresponding to the user.
/// </returns>
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> 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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="type">The token type.</param>
/// <returns>
@ -155,7 +66,7 @@ namespace OpenIddict {
/// </returns>
public virtual Task<string> 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.
/// </returns>
public virtual Task<TToken> FindByIdAsync(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier));
}
return Store.FindByIdAsync(identifier, CancellationToken);
}

201
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 {
/// <summary>
/// Provides methods allowing to manage the users stored in the store.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
public class OpenIddictUserManager<TUser> : UserManager<TUser> where TUser : class {
public OpenIddictUserManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictUserStore<TUser> store,
[NotNull] IOptions<IdentityOptions> options,
[NotNull] ILogger<OpenIddictUserManager<TUser>> logger,
[NotNull] IPasswordHasher<TUser> hasher,
[NotNull] IEnumerable<IUserValidator<TUser>> userValidators,
[NotNull] IEnumerable<IPasswordValidator<TUser>> passwordValidators,
[NotNull] ILookupNormalizer keyNormalizer,
[NotNull] IdentityErrorDescriber errors)
: base(store, options, hasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Options = options.Value;
Store = store;
}
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
protected HttpContext Context { get; }
/// <summary>
/// Gets the identity options.
/// </summary>
protected IdentityOptions Options { get; }
/// <summary>
/// Gets the store associated with the current manager.
/// </summary>
protected new IOpenIddictUserStore<TUser> Store { get; }
/// <summary>
/// Creates a new <see cref="ClaimsIdentity"/> used to create new tokens.
/// </summary>
/// <param name="user">The user corresponding to the identity.</param>
/// <param name="scopes">The scopes granted by the resource owner.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the <see cref="ClaimsIdentity"/> corresponding to the user.
/// </returns>
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> 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;
}
/// <summary>
/// Creates a new token associated with the given user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="type">The token type.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual Task<string> 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);
}
/// <summary>
/// Creates a new token associated with the given user and
/// attached to the tokens issued to the specified client.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="client">The application.</param>
/// <param name="type">The token type.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual Task<string> 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);
}
/// <summary>
/// Retrieves the token identifiers associated with a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens associated with the user.
/// </returns>
public virtual Task<IEnumerable<string>> GetTokensAsync(TUser user) {
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
return Store.GetTokensAsync(user, CancellationToken);
}
}
}

8
src/OpenIddict.Core/OpenIddictBuilder.cs

@ -40,6 +40,12 @@ namespace Microsoft.AspNetCore.Builder {
[EditorBrowsable(EditorBrowsableState.Never)]
public Type AuthorizationType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the Role entity.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Type RoleType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the Scope entity.
/// </summary>
@ -182,7 +188,7 @@ namespace Microsoft.AspNetCore.Builder {
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddTokenManager<TManager>() {
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.");
}

8
src/OpenIddict.Core/OpenIddictExtensions.cs

@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Builder {
/// When using this method, custom stores must be manually registered.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
@ -32,9 +33,10 @@ namespace Microsoft.AspNetCore.Builder {
/// consider adding the MVC module or creating your own authorization controller.
/// </remarks>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TUser, TApplication, TAuthorization, TScope, TToken>(
public static OpenIddictBuilder AddOpenIddict<TUser, TRole, TApplication, TAuthorization, TScope, TToken>(
[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<OpenIddictApplicationManager<TApplication>>();
builder.Services.TryAddScoped<OpenIddictAuthorizationManager<TAuthorization>>();
builder.Services.TryAddScoped<OpenIddictScopeManager<TScope>>();
builder.Services.TryAddScoped<OpenIddictTokenManager<TToken, TUser>>();
builder.Services.TryAddScoped<OpenIddictTokenManager<TToken>>();
builder.Services.TryAddScoped<OpenIddictUserManager<TUser>>();
builder.Services.TryAddScoped<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
return builder;

18
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 {
/// </returns>
Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// </returns>
Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the callback address associated with an application.
/// </summary>
@ -80,14 +92,14 @@ namespace OpenIddict {
Task<string> GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// Retrieves the token identifiers associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// whose result returns the tokens associated with the application.
/// </returns>
Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken);
Task<IEnumerable<string>> GetTokensAsync(TApplication application, CancellationToken cancellationToken);
}
}

2
src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs

@ -14,7 +14,7 @@ namespace OpenIddict {
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
public interface IOpenIddictTokenStore<TToken> where TToken : class {
/// <summary>
/// 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.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>

55
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 {
/// <summary>
/// Provides methods allowing to manage the users stored in a database.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
public interface IOpenIddictUserStore<TUser> : IUserStore<TUser> where TUser : class {
/// <summary>
/// Creates a new token associated with the given user.
/// </summary>
/// <param name="user">The user associated with the token.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
Task<string> CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken);
/// <summary>
/// Creates a new token associated with the given user and
/// attached to the tokens issued to the specified client.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="client">The application.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
Task<string> CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the token identifiers associated with a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens associated with the user.
/// </returns>
Task<IEnumerable<string>> GetTokensAsync(TUser user, CancellationToken cancellationToken);
}
}

16
src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs

@ -5,12 +5,13 @@
*/
using System;
using System.Collections.Generic;
namespace OpenIddict {
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication : OpenIddictApplication<string> {
public class OpenIddictApplication : OpenIddictApplication<string, OpenIddictToken> {
public OpenIddictApplication() {
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
@ -20,7 +21,13 @@ namespace OpenIddict {
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey> where TKey : IEquatable<TKey> {
public class OpenIddictApplication<TKey> : OpenIddictApplication<TKey, OpenIddictToken<TKey>>
where TKey : IEquatable<TKey> { }
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey, TToken> where TKey : IEquatable<TKey> {
/// <summary>
/// Gets or sets the display name
/// associated with the current application.
@ -51,6 +58,11 @@ namespace OpenIddict {
/// </summary>
public virtual string Secret { get; set; }
/// <summary>
/// Gets the list of the tokens associated with this application.
/// </summary>
public virtual IList<TToken> Tokens { get; } = new List<TToken>();
/// <summary>
/// Gets or sets the application type
/// associated with the current application.

7
src/OpenIddict.EntityFramework/OpenIddictContext.cs

@ -107,7 +107,7 @@ namespace OpenIddict {
public class OpenIddictContext<TUser, TRole, TApplication, TAuthorization, TScope, TToken, TKey> : IdentityDbContext<TUser, TRole, TKey>
where TUser : OpenIddictUser<TKey, TAuthorization, TToken>
where TRole : IdentityRole<TKey>
where TApplication : OpenIddictApplication<TKey>
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
@ -158,6 +158,11 @@ namespace OpenIddict {
builder.Entity<TApplication>(entity => {
entity.HasKey(application => application.Id);
entity.HasMany(application => application.Tokens)
.WithOne()
.HasForeignKey("ApplicationId")
.IsRequired(required: false);
entity.ToTable("OpenIddictApplications");
});

20
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)));

50
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.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictApplicationStore<TApplication, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey>
public class OpenIddictApplicationStore<TApplication, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TToken>
where TToken : OpenIddictToken<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictApplicationStore(TContext context) {
@ -129,6 +133,23 @@ namespace OpenIddict {
return Task.FromResult(application.DisplayName);
}
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// </returns>
public virtual Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(application.Secret);
}
/// <summary>
/// Retrieves the callback address associated with an application.
/// </summary>
@ -147,20 +168,37 @@ namespace OpenIddict {
}
/// <summary>
/// Retrieves the hashed secret associated with an application.
/// Retrieves the token identifiers associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// whose result returns the tokens associated with the application.
/// </returns>
public virtual Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) {
public virtual async Task<IEnumerable<string>> 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<string>();
foreach (var identifier in await query.ToArrayAsync()) {
tokens.Add(converter.ConvertToInvariantString(identifier));
}
return tokens;
}
}
}

12
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -15,10 +15,14 @@ namespace OpenIddict {
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictTokenStore<TToken, TContext, TKey> : IOpenIddictTokenStore<TToken>
public class OpenIddictTokenStore<TToken, TAuthorization, TUser, TContext, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TUser : OpenIddictUser<TKey, TAuthorization, TToken>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictTokenStore(TContext context) {
@ -36,7 +40,7 @@ namespace OpenIddict {
protected DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// 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.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
@ -45,6 +49,10 @@ namespace OpenIddict {
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual async Task<string> 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))) {

164
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 {
/// <summary>
/// Provides methods allowing to manage the users stored in a database.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictUserStore<TUser, TApplication, TAuthorization, TRole, TToken, TContext, TKey> :
UserStore<TUser, TRole, TContext, TKey>, IOpenIddictUserStore<TUser>
where TUser : OpenIddictUser<TKey, TAuthorization, TToken>, new()
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TRole : IdentityRole<TKey>
where TToken : OpenIddictToken<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictUserStore(TContext context)
: base(context) { }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Creates a new token associated with the given user and defined by a unique identifier and a token type.
/// </summary>
/// <param name="user">The user associated with the token.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual async Task<string> 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);
}
/// <summary>
/// Creates a new token associated with the given user and
/// attached to the tokens issued to the specified client.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="client">The application.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual async Task<string> 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);
}
/// <summary>
/// Retrieves the token identifiers associated with a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens associated with the user.
/// </returns>
public virtual async Task<IEnumerable<string>> 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<string>();
foreach (var identifier in await query.ToArrayAsync()) {
tokens.Add(converter.ConvertToInvariantString(identifier));
}
return tokens;
}
}
}

5
src/OpenIddict.Mvc/OpenIddictController.cs

@ -70,9 +70,8 @@ namespace OpenIddict.Mvc {
[Authorize, HttpPost, ValidateAntiForgeryToken]
public virtual async Task<IActionResult> Accept(
[FromServices] UserManager<TUser> users,
[FromServices] OpenIddictUserManager<TUser> users,
[FromServices] OpenIddictApplicationManager<TApplication> applications,
[FromServices] OpenIddictTokenManager<TToken, TUser> tokens,
[FromServices] IOptions<OpenIddictOptions> 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);

32
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<IHttpContextAccessor>();
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<IHttpContextAccessor>();
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<IHttpContextAccessor>();
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.

58
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<TUser, OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, TContext, string>();
return services.AddOpenIddict<TUser, IdentityRole, OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, TContext, string>();
}
/// <summary>
@ -66,6 +67,36 @@ namespace Microsoft.AspNetCore.Builder {
/// including the Entity Framework stores and the specified entities.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <param name="services">The services collection.</param>
/// <remarks>
/// 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.
/// </remarks>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TUser, TRole, TContext>([NotNull] this IServiceCollection services)
where TUser : OpenIddictUser
where TRole : IdentityRole
where TContext : DbContext {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
return services.AddOpenIddict<TUser, TRole, OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, TContext, string>();
}
/// <summary>
/// Registers the default OpenIddict services in the DI container,
/// including the Entity Framework stores and the specified entities.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
/// <param name="services">The services collection.</param>
@ -76,18 +107,19 @@ namespace Microsoft.AspNetCore.Builder {
/// consider adding the MVC module or creating your own authorization controller.
/// </remarks>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TUser, TContext, TKey>([NotNull] this IServiceCollection services)
public static OpenIddictBuilder AddOpenIddict<TUser, TRole, TContext, TKey>([NotNull] this IServiceCollection services)
where TUser : OpenIddictUser<TKey>
where TRole : IdentityRole<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
return services.AddOpenIddict<TUser, OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TContext, TKey>();
return services.AddOpenIddict<TUser, TRole, OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TContext, TKey>();
}
/// <summary>
@ -95,6 +127,7 @@ namespace Microsoft.AspNetCore.Builder {
/// including the Entity Framework stores and the specified entities.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
@ -109,10 +142,11 @@ namespace Microsoft.AspNetCore.Builder {
/// consider adding the MVC module or creating your own authorization controller.
/// </remarks>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TUser, TApplication, TAuthorization, TScope, TToken, TContext, TKey>(
public static OpenIddictBuilder AddOpenIddict<TUser, TRole, TApplication, TAuthorization, TScope, TToken, TContext, TKey>(
[NotNull] this IServiceCollection services)
where TUser : OpenIddictUser<TKey, TAuthorization, TToken>
where TApplication : OpenIddictApplication<TKey>
where TRole : IdentityRole<TKey>
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
@ -123,7 +157,7 @@ namespace Microsoft.AspNetCore.Builder {
}
// Register the OpenIddict core services and the default EntityFramework stores.
return services.AddOpenIddict<TUser, TApplication, TAuthorization, TScope, TToken>()
return services.AddOpenIddict<TUser, TRole, TApplication, TAuthorization, TScope, TToken>()
.AddEntityFramework<TContext, TKey>();
}
}

Loading…
Cancel
Save