Browse Source

Introduce OpenIddictConfiguration/OpenIddictServices, decouple OpenIddictStore from UserStore and add new extensions

pull/71/head
Kévin Chalet 10 years ago
parent
commit
5899533ae7
  1. 3
      src/OpenIddict.Core/IOpenIddictStore.cs
  2. 36
      src/OpenIddict.Core/OpenIddictConfiguration.cs
  3. 45
      src/OpenIddict.Core/OpenIddictExtensions.cs
  4. 32
      src/OpenIddict.Core/OpenIddictHelpers.cs
  5. 75
      src/OpenIddict.Core/OpenIddictManager.cs
  6. 20
      src/OpenIddict.Core/OpenIddictProvider.Authentication.cs
  7. 50
      src/OpenIddict.Core/OpenIddictProvider.Exchange.cs
  8. 16
      src/OpenIddict.Core/OpenIddictProvider.Introspection.cs
  9. 4
      src/OpenIddict.Core/OpenIddictProvider.Session.cs
  10. 28
      src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs
  11. 54
      src/OpenIddict.Core/OpenIddictServices.cs
  12. 57
      src/OpenIddict.EF/OpenIddictExtensions.cs
  13. 12
      src/OpenIddict.EF/OpenIddictStore.cs
  14. 39
      src/OpenIddict.Mvc/OpenIddictController.cs
  15. 41
      src/OpenIddict.Mvc/OpenIddictExtensions.cs

3
src/OpenIddict.Core/IOpenIddictStore.cs

@ -1,9 +1,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace OpenIddict {
public interface IOpenIddictStore<TUser, TApplication> : IUserStore<TUser> where TUser : class where TApplication : class {
public interface IOpenIddictStore<TUser, TApplication> where TApplication : class {
Task<TApplication> FindApplicationByIdAsync(string identifier, CancellationToken cancellationToken);
Task<TApplication> FindApplicationByLogoutRedirectUri(string url, CancellationToken cancellationToken);
Task<string> GetApplicationTypeAsync(TApplication application, CancellationToken cancellationToken);

36
src/OpenIddict.Core/OpenIddictConfiguration.cs

@ -0,0 +1,36 @@
/*
* 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 Microsoft.Extensions.DependencyInjection;
namespace OpenIddict {
public class OpenIddictConfiguration {
public OpenIddictConfiguration(IServiceCollection services) {
Services = services;
}
/// <summary>
/// Gets or sets the type corresponding to the Application entity.
/// </summary>
public Type ApplicationType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the Role entity.
/// </summary>
public Type RoleType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the User entity.
/// </summary>
public Type UserType { get; set; }
/// <summary>
/// Gets the services used by OpenIddict.
/// </summary>
public IServiceCollection Services { get; }
}
}

45
src/OpenIddict.Core/OpenIddictExtensions.cs

@ -6,6 +6,7 @@
using System;
using System.Linq;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Hosting;
@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Builder {
public static class OpenIddictExtensions {
public static IdentityBuilder AddOpenIddictCore<TApplication>(
[NotNull] this IdentityBuilder builder,
[NotNull] Action<OpenIddictServices> configuration)
[NotNull] Action<OpenIddictConfiguration> configuration)
where TApplication : class {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
@ -40,19 +41,55 @@ namespace Microsoft.AspNetCore.Builder {
typeof(OpenIddictManager<,>).MakeGenericType(
builder.UserType, typeof(TApplication)));
var services = new OpenIddictServices(builder.Services) {
builder.Services.TryAddTransient(
typeof(OpenIddictServices<,>).MakeGenericType(
builder.UserType, typeof(TApplication)));
var instance = new OpenIddictConfiguration(builder.Services) {
ApplicationType = typeof(TApplication),
RoleType = builder.RoleType,
UserType = builder.UserType
};
builder.Services.TryAddSingleton(services);
builder.Services.TryAddSingleton(instance);
configuration(services);
configuration(instance);
return builder;
}
public static OpenIddictConfiguration UseManager<TManager>([NotNull] this OpenIddictConfiguration configuration) {
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}
var contract = typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType,
configuration.ApplicationType);
if (!contract.IsAssignableFrom(typeof(TManager))) {
throw new InvalidOperationException("Custom managers must be derived from OpenIddictManager.");
}
configuration.Services.Replace(ServiceDescriptor.Scoped(contract, typeof(TManager)));
return configuration;
}
public static OpenIddictConfiguration UseStore<TStore>([NotNull] this OpenIddictConfiguration configuration) {
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}
var contract = typeof(IOpenIddictStore<,>).MakeGenericType(configuration.UserType,
configuration.ApplicationType);
if (!contract.IsAssignableFrom(typeof(TStore))) {
throw new InvalidOperationException("Custom stores must implement IOpenIddictStore.");
}
configuration.Services.Replace(ServiceDescriptor.Scoped(contract, typeof(TStore)));
return configuration;
}
public static OpenIddictBuilder AddModule(
[NotNull] this OpenIddictBuilder builder,
[NotNull] string name, int position,

32
src/OpenIddict.Core/OpenIddictHelpers.cs

@ -1,10 +1,12 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Identity;
namespace OpenIddict {
public static class OpenIddictHelpers {
public static async Task<bool> IsConfidentialApplicationAsync<TUser, TApplication>(
this OpenIddictManager<TUser, TApplication> manager, TApplication application)
[NotNull] this OpenIddictManager<TUser, TApplication> manager, [NotNull] TApplication application)
where TUser : class
where TApplication : class {
if (manager == null) {
@ -21,7 +23,7 @@ namespace OpenIddict {
}
public static async Task<bool> IsPublicApplicationAsync<TUser, TApplication>(
this OpenIddictManager<TUser, TApplication> manager, TApplication application)
[NotNull] this OpenIddictManager<TUser, TApplication> manager, [NotNull] TApplication application)
where TUser : class
where TApplication : class {
if (manager == null) {
@ -36,5 +38,31 @@ namespace OpenIddict {
return string.Equals(type, OpenIddictConstants.ApplicationTypes.Public, StringComparison.OrdinalIgnoreCase);
}
public static async Task<string> FindClaimAsync<TUser>(
[NotNull] this UserManager<TUser> manager,
[NotNull] TUser user, [NotNull] string type) where TUser : class {
if (manager == null) {
throw new ArgumentNullException(nameof(manager));
}
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrEmpty(type)) {
throw new ArgumentNullException(nameof(type));
}
// Note: GetClaimsAsync will automatically throw an exception
// if the underlying store doesn't support custom claims.
var claims = await manager.GetClaimsAsync(user);
if (claims.Count != 0) {
return claims[0]?.Value;
}
return null;
}
}
}

75
src/OpenIddict.Core/OpenIddictManager.cs

@ -7,54 +7,48 @@ using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using CryptoHelper;
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 {
public class OpenIddictManager<TUser, TApplication> : UserManager<TUser> where TUser : class where TApplication : class {
public OpenIddictManager(
IOpenIddictStore<TUser, TApplication> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TUser> passwordHasher,
IEnumerable<IUserValidator<TUser>> userValidators,
IEnumerable<IPasswordValidator<TUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<TUser>> logger)
: base(store, optionsAccessor,
passwordHasher, userValidators,
passwordValidators, keyNormalizer,
errors, services, logger) {
Context = services.GetService<IHttpContextAccessor>()?.HttpContext;
Options = optionsAccessor.Value;
public class OpenIddictManager<TUser, TApplication> where TUser : class where TApplication : class {
public OpenIddictManager([NotNull] OpenIddictServices<TUser, TApplication> services) {
Services = services;
}
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
public virtual HttpContext Context { get; }
protected virtual HttpContext Context => Services.Context;
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
public virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
protected virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the Identity options associated with the current manager.
/// Gets the logger associated with the current manager.
/// </summary>
public virtual IdentityOptions Options { get; }
protected virtual ILogger Logger => Services.Logger;
/// <summary>
/// Gets the options associated with the current manager.
/// </summary>
protected virtual IdentityOptions Options => Services.Services.GetRequiredService<IOptions<IdentityOptions>>().Value;
/// <summary>
/// Gets the servuces associated with the current manager.
/// </summary>
protected virtual OpenIddictServices<TUser, TApplication> Services { get; }
/// <summary>
/// Gets the store associated with the current manager.
/// </summary>
public virtual new IOpenIddictStore<TUser, TApplication> Store {
get { return base.Store as IOpenIddictStore<TUser, TApplication>; }
}
protected virtual IOpenIddictStore<TUser, TApplication> Store => Services.Store;
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> scopes) {
if (user == null) {
@ -72,11 +66,11 @@ namespace OpenIddict {
// 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));
identity.AddClaim(ClaimTypes.NameIdentifier, await Services.Users.GetUserIdAsync(user));
// Resolve the username and the email address associated with the user.
var username = await GetUserNameAsync(user);
var email = await GetEmailAsync(user);
var username = await Services.Users.GetUserNameAsync(user);
var email = await Services.Users.GetEmailAsync(user);
// Only add the name claim if the "profile" scope was granted.
if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) {
@ -99,16 +93,16 @@ namespace OpenIddict {
OpenIdConnectConstants.Destinations.IdentityToken);
}
if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) {
foreach (var role in await GetRolesAsync(user)) {
if (Services.Users.SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) {
foreach (var role in await Services.Users.GetRolesAsync(user)) {
identity.AddClaim(identity.RoleClaimType, role,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
}
if (SupportsUserSecurityStamp) {
var identifier = await GetSecurityStampAsync(user);
if (Services.Users.SupportsUserSecurityStamp) {
var identifier = await Services.Users.GetSecurityStampAsync(user);
if (!string.IsNullOrEmpty(identifier)) {
identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, identifier,
@ -128,23 +122,6 @@ namespace OpenIddict {
return Store.FindApplicationByLogoutRedirectUri(url, CancellationToken);
}
public virtual async Task<string> FindClaimAsync(TUser user, string type) {
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrEmpty(type)) {
throw new ArgumentNullException(nameof(type));
}
// Note: GetClaimsAsync will automatically throw an exception
// if the underlying store doesn't support custom claims.
return (from claim in await GetClaimsAsync(user)
where string.Equals(claim.Type, type, StringComparison.Ordinal)
select claim.Value).FirstOrDefault();
}
public virtual async Task<string> GetApplicationTypeAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));

20
src/OpenIddict.Core/OpenIddictProvider.Authentication.cs

@ -20,7 +20,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Note: redirect_uri is not required for pure OAuth2 requests
// but this provider uses a stricter policy making it mandatory,
@ -35,7 +35,7 @@ namespace OpenIddict {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await manager.FindApplicationByIdAsync(context.ClientId);
var application = await services.Applications.FindApplicationByIdAsync(context.ClientId);
if (application == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -44,7 +44,7 @@ namespace OpenIddict {
return;
}
if (!await manager.ValidateRedirectUriAsync(application, context.RedirectUri)) {
if (!await services.Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid redirect_uri.");
@ -56,7 +56,7 @@ namespace OpenIddict {
// flow are rejected if the client identifier corresponds to a confidential application.
// Note: when using the authorization code grant, ValidateClientAuthentication is responsible of
// rejecting the token request if the client_id corresponds to an unauthenticated confidential client.
if (await manager.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) {
if (await services.Applications.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Confidential clients can only use response_type=code.");
@ -68,7 +68,7 @@ namespace OpenIddict {
// the appropriate set of scopes is requested to prevent personal data leakage.
if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) {
// Ensure the user profile still exists in the database.
var user = await manager.GetUserAsync(context.HttpContext.User);
var user = await services.Users.GetUserAsync(context.HttpContext.User);
if (user == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.ServerError,
@ -81,8 +81,8 @@ namespace OpenIddict {
// email address and if the "email" scope has not been requested.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) &&
string.Equals(await manager.GetUserNameAsync(user),
await manager.GetEmailAsync(user),
string.Equals(await services.Users.GetUserNameAsync(user),
await services.Users.GetEmailAsync(user),
StringComparison.OrdinalIgnoreCase)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -136,7 +136,7 @@ namespace OpenIddict {
return;
}
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest
// rejects prompt=none requests missing or having an invalid id_token_hint.
@ -147,7 +147,7 @@ namespace OpenIddict {
// the initial check made by ValidateAuthorizationRequest.
// In this case, ignore the prompt=none request and
// continue to the next middleware in the pipeline.
var user = await manager.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
return;
}
@ -155,7 +155,7 @@ namespace OpenIddict {
// 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 manager.CreateIdentityAsync(user, context.Request.GetScopes());
var identity = await services.Applications.CreateIdentityAsync(user, context.Request.GetScopes());
Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity.

50
src/OpenIddict.Core/OpenIddictProvider.Exchange.cs

@ -20,7 +20,7 @@ using Microsoft.Extensions.Options;
namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Note: OpenIdConnectServerHandler supports authorization code, refresh token,
// client credentials, resource owner password credentials and custom grants
@ -53,7 +53,7 @@ namespace OpenIddict {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await manager.FindApplicationByIdAsync(context.ClientId);
var application = await services.Applications.FindApplicationByIdAsync(context.ClientId);
if (application == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -63,7 +63,7 @@ namespace OpenIddict {
}
// Reject tokens requests containing a client_secret if the client application is not confidential.
if (await manager.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) {
if (await services.Applications.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Public clients are not allowed to send a client_secret.");
@ -73,7 +73,7 @@ namespace OpenIddict {
// Confidential applications MUST authenticate
// to protect them from impersonation attacks.
else if (await manager.IsConfidentialApplicationAsync(application)) {
else if (await services.Applications.IsConfidentialApplicationAsync(application)) {
if (string.IsNullOrEmpty(context.ClientSecret)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -82,7 +82,7 @@ namespace OpenIddict {
return;
}
if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) {
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");
@ -95,10 +95,10 @@ namespace OpenIddict {
}
public override async Task GrantClientCredentials([NotNull] GrantClientCredentialsContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Retrieve the application details corresponding to the requested client_id.
var application = await manager.FindApplicationByIdAsync(context.ClientId);
var application = await services.Applications.FindApplicationByIdAsync(context.ClientId);
Debug.Assert(application != null);
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
@ -107,7 +107,7 @@ namespace OpenIddict {
// access tokens, even if an explicit destination is not specified.
identity.AddClaim(ClaimTypes.NameIdentifier, context.ClientId);
identity.AddClaim(ClaimTypes.Name, await manager.GetDisplayNameAsync(application),
identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
@ -125,13 +125,13 @@ namespace OpenIddict {
}
public override async Task GrantRefreshToken([NotNull] GrantRefreshTokenContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>();
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
var user = await manager.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -142,10 +142,10 @@ namespace OpenIddict {
// If the user manager supports security stamps,
// ensure that the refresh token is still valid.
if (manager.SupportsUserSecurityStamp) {
if (services.Users.SupportsUserSecurityStamp) {
var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType);
if (!string.IsNullOrEmpty(identifier) &&
!string.Equals(identifier, await manager.GetSecurityStampAsync(user), StringComparison.Ordinal)) {
!string.Equals(identifier, await services.Users.GetSecurityStampAsync(user), StringComparison.Ordinal)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid.");
@ -156,7 +156,7 @@ namespace OpenIddict {
// 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 manager.CreateIdentityAsync(user, context.Ticket.GetScopes());
var identity = await services.Applications.CreateIdentityAsync(user, context.Ticket.GetScopes());
Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity but
@ -170,9 +170,9 @@ namespace OpenIddict {
}
public override async Task GrantResourceOwnerCredentials([NotNull] GrantResourceOwnerCredentialsContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
var user = await manager.FindByNameAsync(context.UserName);
var user = await services.Users.FindByNameAsync(context.UserName);
if (user == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -182,7 +182,7 @@ namespace OpenIddict {
}
// Ensure the user is not already locked out.
if (manager.SupportsUserLockout && await manager.IsLockedOutAsync(user)) {
if (services.Users.SupportsUserLockout && await services.Users.IsLockedOutAsync(user)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
@ -191,16 +191,16 @@ namespace OpenIddict {
}
// Ensure the password is valid.
if (!await manager.CheckPasswordAsync(user, context.Password)) {
if (!await services.Users.CheckPasswordAsync(user, context.Password)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
if (manager.SupportsUserLockout) {
await manager.AccessFailedAsync(user);
if (services.Users.SupportsUserLockout) {
await services.Users.AccessFailedAsync(user);
// Ensure the user is not locked out.
if (await manager.IsLockedOutAsync(user)) {
if (await services.Users.IsLockedOutAsync(user)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
@ -210,16 +210,16 @@ namespace OpenIddict {
return;
}
if (manager.SupportsUserLockout) {
await manager.ResetAccessFailedCountAsync(user);
if (services.Users.SupportsUserLockout) {
await services.Users.ResetAccessFailedCountAsync(user);
}
// Return an error if the username corresponds to the registered
// email address and if the "email" scope has not been requested.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) &&
string.Equals(await manager.GetUserNameAsync(user),
await manager.GetEmailAsync(user),
string.Equals(await services.Users.GetUserNameAsync(user),
await services.Users.GetEmailAsync(user),
StringComparison.OrdinalIgnoreCase)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -228,7 +228,7 @@ namespace OpenIddict {
return;
}
var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes());
var identity = await services.Applications.CreateIdentityAsync(user, context.Request.GetScopes());
Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity.

16
src/OpenIddict.Core/OpenIddictProvider.Introspection.cs

@ -17,7 +17,7 @@ using Microsoft.Extensions.Options;
namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Note: ASOS supports both GET and POST introspection requests but OpenIddict only accepts POST requests.
if (!string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) {
@ -41,7 +41,7 @@ namespace OpenIddict {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await manager.FindApplicationByIdAsync(context.ClientId);
var application = await services.Applications.FindApplicationByIdAsync(context.ClientId);
if (application == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -51,7 +51,7 @@ namespace OpenIddict {
}
// Reject non-confidential applications.
if (await manager.IsPublicApplicationAsync(application)) {
if (await services.Applications.IsPublicApplicationAsync(application)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Public applications are not allowed to use the introspection endpoint.");
@ -60,7 +60,7 @@ namespace OpenIddict {
}
// Validate the client credentials.
if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) {
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");
@ -72,19 +72,19 @@ namespace OpenIddict {
}
public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>();
// If the user manager doesn't support security
// stamps, skip the additional validation logic.
if (!manager.SupportsUserSecurityStamp) {
if (!services.Users.SupportsUserSecurityStamp) {
return;
}
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
var user = await manager.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
context.Active = false;
@ -93,7 +93,7 @@ namespace OpenIddict {
var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType);
if (!string.IsNullOrEmpty(identifier) &&
!string.Equals(identifier, await manager.GetSecurityStampAsync(user), StringComparison.Ordinal)) {
!string.Equals(identifier, await services.Users.GetSecurityStampAsync(user), StringComparison.Ordinal)) {
context.Active = false;
return;

4
src/OpenIddict.Core/OpenIddictProvider.Session.cs

@ -13,7 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateLogoutRequest([NotNull] ValidateLogoutRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
// Skip validation if the optional post_logout_redirect_uri
// parameter was missing from the logout request.
@ -23,7 +23,7 @@ namespace OpenIddict {
return;
}
var application = await manager.FindApplicationByLogoutRedirectUri(context.PostLogoutRedirectUri);
var application = await services.Applications.FindApplicationByLogoutRedirectUri(context.PostLogoutRedirectUri);
if (application == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,

28
src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs

@ -16,14 +16,14 @@ using Newtonsoft.Json.Linq;
namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) {
var manager = context.HttpContext.RequestServices.GetRequiredService<OpenIddictManager<TUser, TApplication>>();
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication>>();
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
// Note: user may be null if the user has been removed.
// In this case, return a 400 response.
var user = await manager.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
context.Response.StatusCode = 400;
context.HandleResponse();
@ -33,47 +33,47 @@ namespace OpenIddict {
// Note: "sub" is a mandatory claim.
// See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
context.Subject = await manager.GetUserIdAsync(user);
context.Subject = await services.Users.GetUserIdAsync(user);
// Only add the "preferred_username" claim if the "profile" scope was present in the access token.
// 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.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) {
context.PreferredUsername = await manager.GetUserNameAsync(user);
context.PreferredUsername = await services.Users.GetUserNameAsync(user);
if (manager.SupportsUserClaim) {
context.FamilyName = await manager.FindClaimAsync(user, ClaimTypes.Surname);
context.GivenName = await manager.FindClaimAsync(user, ClaimTypes.GivenName);
context.BirthDate = await manager.FindClaimAsync(user, ClaimTypes.DateOfBirth);
if (services.Users.SupportsUserClaim) {
context.FamilyName = await services.Users.FindClaimAsync(user, ClaimTypes.Surname);
context.GivenName = await services.Users.FindClaimAsync(user, ClaimTypes.GivenName);
context.BirthDate = await services.Users.FindClaimAsync(user, ClaimTypes.DateOfBirth);
}
}
// Only add the email address details if the "email" scope was present in the access token.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) {
context.Email = await manager.GetEmailAsync(user);
context.Email = await services.Users.GetEmailAsync(user);
// Only add the "email_verified" claim
// if the email address is non-null.
if (!string.IsNullOrEmpty(context.Email)) {
context.EmailVerified = await manager.IsEmailConfirmedAsync(user);
context.EmailVerified = await services.Users.IsEmailConfirmedAsync(user);
}
};
// Only add the phone number details if the "phone" scope was present in the access token.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) {
context.PhoneNumber = await manager.GetPhoneNumberAsync(user);
context.PhoneNumber = await services.Users.GetPhoneNumberAsync(user);
// Only add the "phone_number_verified"
// claim if the phone number is non-null.
if (!string.IsNullOrEmpty(context.PhoneNumber)) {
context.PhoneNumberVerified = await manager.IsPhoneNumberConfirmedAsync(user);
context.PhoneNumberVerified = await services.Users.IsPhoneNumberConfirmedAsync(user);
}
}
// Only add the roles list if the "roles" scope was present in the access token.
if (manager.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) {
var roles = await manager.GetRolesAsync(user);
if (services.Users.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) {
var roles = await services.Users.GetRolesAsync(user);
if (roles.Count != 0) {
context.Claims[OpenIddictConstants.Claims.Roles] = JArray.FromObject(roles);
}

54
src/OpenIddict.Core/OpenIddictServices.cs

@ -5,32 +5,66 @@
*/
using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict {
public class OpenIddictServices {
public OpenIddictServices(IServiceCollection services) {
/// <summary>
/// Exposes the common services used by OpenIddict.
/// </summary>
public class OpenIddictServices<TUser, TApplication> where TUser : class where TApplication : class {
public OpenIddictServices([NotNull] IServiceProvider services) {
Services = services;
}
/// <summary>
/// Gets or sets the type corresponding to the Application entity.
/// Gets the <see cref="OpenIddictManager{TUser, TApplication}"/>.
/// </summary>
public Type ApplicationType { get; set; }
public virtual OpenIddictManager<TUser, TApplication> Applications {
get { return Services.GetRequiredService<OpenIddictManager<TUser, TApplication>>(); }
}
/// <summary>
/// Gets the optional <see cref="HttpContext"/>.
/// </summary>
public virtual HttpContext Context {
get { return Services.GetService<IHttpContextAccessor>()?.HttpContext; }
}
/// <summary>
/// Gets or sets the type corresponding to the Role entity.
/// Gets the <see cref="ILogger"/>.
/// </summary>
public Type RoleType { get; set; }
public virtual ILogger Logger {
get { return Services.GetRequiredService<ILogger<OpenIddictManager<TUser, TApplication>>>(); }
}
/// <summary>
/// Gets or sets the type corresponding to the User entity.
/// Gets the <see cref="IServiceProvider"/> used to resolve services.
/// </summary>
public Type UserType { get; set; }
public virtual IServiceProvider Services { get; }
/// <summary>
/// Gets the services used by OpenIddict.
/// Gets the <see cref="SignInManager{TUser}"/>.
/// </summary>
public IServiceCollection Services { get; }
public virtual SignInManager<TUser> SignIn {
get { return Services.GetRequiredService<SignInManager<TUser>>(); }
}
/// <summary>
/// Gets the <see cref="IOpenIddictStore{TUser, TApplication}"/>.
/// </summary>
public virtual IOpenIddictStore<TUser, TApplication> Store {
get { return Services.GetRequiredService<IOpenIddictStore<TUser, TApplication>>(); }
}
/// <summary>
/// Gets the <see cref="UserManager{TUser}"/>.
/// </summary>
public virtual UserManager<TUser> Users {
get { return Services.GetRequiredService<UserManager<TUser>>(); }
}
}
}

57
src/OpenIddict.EF/OpenIddictExtensions.cs

@ -13,32 +13,37 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict;
using OpenIddict.Models;
namespace Microsoft.AspNetCore.Builder {
public static class OpenIddictExtensions {
public static OpenIddictServices UseEntityFramework([NotNull] this OpenIddictServices services) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
public static OpenIddictConfiguration UseEntityFramework([NotNull] this OpenIddictConfiguration configuration) {
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}
services.Services.AddScoped(
typeof(IOpenIddictStore<,>).MakeGenericType(services.UserType, services.ApplicationType),
typeof(OpenIddictStore<,,,,>).MakeGenericType(
/* TUser: */ services.UserType,
/* TApplication: */ services.ApplicationType,
/* TRole: */ services.RoleType,
/* TContext: */ ResolveContextType(services),
/* TKey: */ ResolveKeyType(services)));
if (!IsSubclassOf(configuration.ApplicationType, typeof(Application<>))) {
throw new InvalidOperationException("The default store cannot be used with application " +
"entities that are not derived from Application<TKey>.");
}
configuration.Services.AddScoped(
typeof(IOpenIddictStore<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType),
typeof(OpenIddictStore<,,,>).MakeGenericType(
/* TUser: */ configuration.UserType,
/* TApplication: */ configuration.ApplicationType,
/* TContext: */ ResolveContextType(configuration),
/* TKey: */ ResolveKeyType(configuration)));
return services;
return configuration;
}
private static Type ResolveContextType([NotNull] OpenIddictServices services) {
var service = (from registration in services.Services
private static Type ResolveContextType([NotNull] OpenIddictConfiguration configuration) {
var service = (from registration in configuration.Services
where registration.ServiceType.IsConstructedGenericType
let definition = registration.ServiceType.GetGenericTypeDefinition()
where definition == typeof(IUserStore<>)
select registration.ImplementationType).SingleOrDefault();
select registration.ImplementationType).FirstOrDefault();
if (service == null) {
throw new InvalidOperationException(
@ -69,9 +74,9 @@ namespace Microsoft.AspNetCore.Builder {
throw new InvalidOperationException("The type of the database context cannot be automatically inferred.");
}
private static Type ResolveKeyType([NotNull] OpenIddictServices services) {
private static Type ResolveKeyType([NotNull] OpenIddictConfiguration configuration) {
TypeInfo type;
for (type = services.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) {
for (type = configuration.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) {
if (!type.IsGenericType) {
continue;
}
@ -90,7 +95,23 @@ namespace Microsoft.AspNetCore.Builder {
throw new InvalidOperationException(
"The type of the key identifier used by the user " +
$"entity '{services.UserType}' cannot be automatically inferred.");
$"entity '{configuration.UserType}' cannot be automatically inferred.");
}
private static bool IsSubclassOf([NotNull] Type type, [NotNull] Type generic) {
while (type != null && type != typeof(object)) {
var current = type.GetTypeInfo().IsGenericType ?
type.GetGenericTypeDefinition() :
type;
if (current == generic) {
return true;
}
type = type.GetTypeInfo().BaseType;
}
return false;
}
}
}

12
src/OpenIddict.EF/OpenIddictStore.cs

@ -6,16 +6,20 @@ using Microsoft.EntityFrameworkCore;
using OpenIddict.Models;
namespace OpenIddict {
public class OpenIddictStore<TUser, TApplication, TRole, TContext, TKey> : UserStore<TUser, TRole, TContext, TKey>, IOpenIddictStore<TUser, TApplication>
public class OpenIddictStore<TUser, TApplication, TContext, TKey> : IOpenIddictStore<TUser, TApplication>
where TUser : IdentityUser<TKey>
where TApplication : Application
where TRole : IdentityRole<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictStore(TContext context)
: base(context) {
public OpenIddictStore(TContext context) {
Context = context;
}
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
public virtual TContext Context { get; }
public DbSet<TApplication> Applications {
get { return Context.Set<TApplication>(); }
}

39
src/OpenIddict.Mvc/OpenIddictController.cs

@ -18,22 +18,23 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace OpenIddict.Mvc {
// Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it.
public class OpenIddictController<TUser, TApplication> : Controller where TUser : class where TApplication : class {
public OpenIddictController(
[NotNull] OpenIddictManager<TUser, TApplication> manager,
[NotNull] OpenIddictOptions options) {
Manager = manager;
Options = options;
[NotNull] OpenIddictServices<TUser, TApplication> services,
[NotNull] IOptions<OpenIddictOptions> options) {
Services = services;
Options = options.Value;
}
/// <summary>
/// Gets the OpenIddict manager used by the controller.
/// Gets the OpenIddict services used by the controller.
/// </summary>
protected virtual OpenIddictManager<TUser, TApplication> Manager { get; }
protected virtual OpenIddictServices<TUser, TApplication> Services { get; }
/// <summary>
/// Gets the OpenIddict options used by the server.
@ -71,11 +72,10 @@ namespace OpenIddict.Mvc {
});
}
// Note: ASOS automatically ensures that an application corresponds to the client_id specified
// in the authorization request by calling IOpenIdConnectServerProvider.ValidateAuthorizationRequest.
// In theory, this null check shouldn't be needed, but a race condition could occur if you
// manually removed the application details from the database after the initial check made by ASOS.
var application = await Manager.FindApplicationByIdAsync(request.ClientId);
// Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application
// corresponds to the client_id specified in the authorization request using
// IOpenIdConnectServerProvider.ValidateClientRedirectUri (see OpenIddictProvider.cs).
var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId);
if (application == null) {
return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.InvalidClient,
@ -83,7 +83,7 @@ namespace OpenIddict.Mvc {
});
}
return View("Authorize", Tuple.Create(request, await Manager.GetDisplayNameAsync(application)));
return View("Authorize", Tuple.Create(request, await Services.Applications.GetDisplayNameAsync(application)));
}
[Authorize, HttpPost, ValidateAntiForgeryToken]
@ -102,7 +102,7 @@ namespace OpenIddict.Mvc {
}
// Retrieve the user data using the unique identifier.
var user = await Manager.GetUserAsync(User);
var user = await Services.Users.GetUserAsync(User);
if (user == null) {
return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.ServerError,
@ -112,17 +112,10 @@ 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 Manager.CreateIdentityAsync(user, request.GetScopes());
var identity = await Services.Applications.CreateIdentityAsync(user, request.GetScopes());
Debug.Assert(identity != null);
// Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application
// corresponds to the client_id specified in the authorization request using
// IOpenIdConnectServerProvider.ValidateClientRedirectUri (see OpenIddictProvider.cs).
var application = await Manager.FindApplicationByIdAsync(request.ClientId);
// In theory, this null check is thus not strictly necessary. That said, a race condition
// and a null reference exception could appear here if you manually removed the application
// details from the database after the initial check made by AspNet.Security.OpenIdConnect.Server.
var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId);
if (application == null) {
return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.InvalidClient,
@ -136,7 +129,7 @@ namespace OpenIddict.Mvc {
identity.Actor = new ClaimsIdentity(Options.AuthenticationScheme);
identity.Actor.AddClaim(ClaimTypes.NameIdentifier, request.ClientId);
identity.Actor.AddClaim(ClaimTypes.Name, await Manager.GetDisplayNameAsync(application),
identity.Actor.AddClaim(ClaimTypes.Name, await Services.Applications.GetDisplayNameAsync(application),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);

41
src/OpenIddict.Mvc/OpenIddictExtensions.cs

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using OpenIddict;
using OpenIddict.Mvc;
@ -49,12 +50,12 @@ namespace Microsoft.AspNetCore.Builder {
});
}
}), services => {
var registration = app.ApplicationServices.GetRequiredService<OpenIddictServices>();
var configuration = app.ApplicationServices.GetRequiredService<OpenIddictConfiguration>();
services.AddMvc()
// Register the OpenIddict controller.
.AddControllersAsServices(new[] {
typeof(OpenIddictController<,>).MakeGenericType(registration.UserType, registration.ApplicationType)
typeof(OpenIddictController<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType)
})
// Add an OpenIddict-specific convention to ensure that the generic
@ -69,30 +70,44 @@ namespace Microsoft.AspNetCore.Builder {
baseNamespace: typeof(OpenIddictController<,>).Namespace));
});
// Register the user manager in the isolated container.
services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType), provider => {
var accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null);
// Resolve the user manager from the parent container.
return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType));
});
// Register the services context in the isolated container.
services.AddScoped(typeof(OpenIddictServices<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType), provider => {
var accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null);
// Resolve the services context from the parent container.
return container.GetRequiredService(typeof(OpenIddictServices<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType));
});
// Register the sign-in manager in the isolated container.
services.AddScoped(typeof(SignInManager<>).MakeGenericType(registration.UserType), provider => {
services.AddScoped(typeof(SignInManager<>).MakeGenericType(configuration.UserType), provider => {
var accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null);
// Resolve the sign-in manager from the parent container.
return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(registration.UserType));
return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(configuration.UserType));
});
// Register the user manager in the isolated container.
services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(registration.UserType, registration.ApplicationType), provider => {
services.AddScoped(typeof(UserManager<>).MakeGenericType(configuration.UserType), provider => {
var accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null);
// Resolve the user manager from the parent container.
return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(registration.UserType, registration.ApplicationType));
});
// Register the user manager in the isolated container.
services.AddScoped(typeof(UserManager<>).MakeGenericType(registration.UserType), provider => {
return provider.GetRequiredService(typeof(OpenIddictManager<,>)
.MakeGenericType(registration.UserType, registration.ApplicationType));
return container.GetRequiredService(typeof(UserManager<>).MakeGenericType(configuration.UserType));
});
// Register the assembly provider in the isolated container.
@ -116,7 +131,7 @@ namespace Microsoft.AspNetCore.Builder {
});
// Register the options in the isolated container.
services.AddScoped(provider => builder.Options);
services.AddSingleton(Options.Create(builder.Options));
}));
}

Loading…
Cancel
Save