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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace OpenIddict { 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> FindApplicationByIdAsync(string identifier, CancellationToken cancellationToken);
Task<TApplication> FindApplicationByLogoutRedirectUri(string url, CancellationToken cancellationToken); Task<TApplication> FindApplicationByLogoutRedirectUri(string url, CancellationToken cancellationToken);
Task<string> GetApplicationTypeAsync(TApplication application, 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;
using System.Linq; using System.Linq;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Server; using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Builder {
public static class OpenIddictExtensions { public static class OpenIddictExtensions {
public static IdentityBuilder AddOpenIddictCore<TApplication>( public static IdentityBuilder AddOpenIddictCore<TApplication>(
[NotNull] this IdentityBuilder builder, [NotNull] this IdentityBuilder builder,
[NotNull] Action<OpenIddictServices> configuration) [NotNull] Action<OpenIddictConfiguration> configuration)
where TApplication : class { where TApplication : class {
if (builder == null) { if (builder == null) {
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
@ -40,19 +41,55 @@ namespace Microsoft.AspNetCore.Builder {
typeof(OpenIddictManager<,>).MakeGenericType( typeof(OpenIddictManager<,>).MakeGenericType(
builder.UserType, typeof(TApplication))); 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), ApplicationType = typeof(TApplication),
RoleType = builder.RoleType, RoleType = builder.RoleType,
UserType = builder.UserType UserType = builder.UserType
}; };
builder.Services.TryAddSingleton(services); builder.Services.TryAddSingleton(instance);
configuration(services); configuration(instance);
return builder; 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( public static OpenIddictBuilder AddModule(
[NotNull] this OpenIddictBuilder builder, [NotNull] this OpenIddictBuilder builder,
[NotNull] string name, int position, [NotNull] string name, int position,

32
src/OpenIddict.Core/OpenIddictHelpers.cs

@ -1,10 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Identity;
namespace OpenIddict { namespace OpenIddict {
public static class OpenIddictHelpers { public static class OpenIddictHelpers {
public static async Task<bool> IsConfidentialApplicationAsync<TUser, TApplication>( 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 TUser : class
where TApplication : class { where TApplication : class {
if (manager == null) { if (manager == null) {
@ -21,7 +23,7 @@ namespace OpenIddict {
} }
public static async Task<bool> IsPublicApplicationAsync<TUser, TApplication>( 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 TUser : class
where TApplication : class { where TApplication : class {
if (manager == null) { if (manager == null) {
@ -36,5 +38,31 @@ namespace OpenIddict {
return string.Equals(type, OpenIddictConstants.ApplicationTypes.Public, StringComparison.OrdinalIgnoreCase); 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.Extensions;
using AspNet.Security.OpenIdConnect.Server; using AspNet.Security.OpenIdConnect.Server;
using CryptoHelper; using CryptoHelper;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace OpenIddict { namespace OpenIddict {
public class OpenIddictManager<TUser, TApplication> : UserManager<TUser> where TUser : class where TApplication : class { public class OpenIddictManager<TUser, TApplication> where TUser : class where TApplication : class {
public OpenIddictManager( public OpenIddictManager([NotNull] OpenIddictServices<TUser, TApplication> services) {
IOpenIddictStore<TUser, TApplication> store, Services = services;
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;
} }
/// <summary> /// <summary>
/// Gets the HTTP context associated with the current manager. /// Gets the HTTP context associated with the current manager.
/// </summary> /// </summary>
public virtual HttpContext Context { get; } protected virtual HttpContext Context => Services.Context;
/// <summary> /// <summary>
/// Gets the cancellation token used to abort async operations. /// Gets the cancellation token used to abort async operations.
/// </summary> /// </summary>
public virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; protected virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary> /// <summary>
/// Gets the Identity options associated with the current manager. /// Gets the logger associated with the current manager.
/// </summary> /// </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> /// <summary>
/// Gets the store associated with the current manager. /// Gets the store associated with the current manager.
/// </summary> /// </summary>
public virtual new IOpenIddictStore<TUser, TApplication> Store { protected virtual IOpenIddictStore<TUser, TApplication> Store => Services.Store;
get { return base.Store as IOpenIddictStore<TUser, TApplication>; }
}
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> scopes) { public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> scopes) {
if (user == null) { if (user == null) {
@ -72,11 +66,11 @@ namespace OpenIddict {
// Note: the name identifier is always included in both identity and // Note: the name identifier is always included in both identity and
// access tokens, even if an explicit destination is not specified. // 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. // Resolve the username and the email address associated with the user.
var username = await GetUserNameAsync(user); var username = await Services.Users.GetUserNameAsync(user);
var email = await GetEmailAsync(user); var email = await Services.Users.GetEmailAsync(user);
// Only add the name claim if the "profile" scope was granted. // Only add the name claim if the "profile" scope was granted.
if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) {
@ -99,16 +93,16 @@ namespace OpenIddict {
OpenIdConnectConstants.Destinations.IdentityToken); OpenIdConnectConstants.Destinations.IdentityToken);
} }
if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { if (Services.Users.SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) {
foreach (var role in await GetRolesAsync(user)) { foreach (var role in await Services.Users.GetRolesAsync(user)) {
identity.AddClaim(identity.RoleClaimType, role, identity.AddClaim(identity.RoleClaimType, role,
OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken); OpenIdConnectConstants.Destinations.IdentityToken);
} }
} }
if (SupportsUserSecurityStamp) { if (Services.Users.SupportsUserSecurityStamp) {
var identifier = await GetSecurityStampAsync(user); var identifier = await Services.Users.GetSecurityStampAsync(user);
if (!string.IsNullOrEmpty(identifier)) { if (!string.IsNullOrEmpty(identifier)) {
identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, identifier, identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, identifier,
@ -128,23 +122,6 @@ namespace OpenIddict {
return Store.FindApplicationByLogoutRedirectUri(url, CancellationToken); 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) { public virtual async Task<string> GetApplicationTypeAsync(TApplication application) {
if (application == null) { if (application == null) {
throw new ArgumentNullException(nameof(application)); throw new ArgumentNullException(nameof(application));

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

@ -20,7 +20,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict { namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class { public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { 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 // Note: redirect_uri is not required for pure OAuth2 requests
// but this provider uses a stricter policy making it mandatory, // 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. // 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) { if (application == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
@ -44,7 +44,7 @@ namespace OpenIddict {
return; return;
} }
if (!await manager.ValidateRedirectUriAsync(application, context.RedirectUri)) { if (!await services.Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid redirect_uri."); description: "Invalid redirect_uri.");
@ -56,7 +56,7 @@ namespace OpenIddict {
// flow are rejected if the client identifier corresponds to a confidential application. // flow are rejected if the client identifier corresponds to a confidential application.
// Note: when using the authorization code grant, ValidateClientAuthentication is responsible of // 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. // 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( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Confidential clients can only use response_type=code."); 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. // the appropriate set of scopes is requested to prevent personal data leakage.
if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) { if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) {
// Ensure the user profile still exists in the database. // 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) { if (user == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.ServerError, error: OpenIdConnectConstants.Errors.ServerError,
@ -81,8 +81,8 @@ namespace OpenIddict {
// email address and if the "email" scope has not been requested. // email address and if the "email" scope has not been requested.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) &&
string.Equals(await manager.GetUserNameAsync(user), string.Equals(await services.Users.GetUserNameAsync(user),
await manager.GetEmailAsync(user), await services.Users.GetEmailAsync(user),
StringComparison.OrdinalIgnoreCase)) { StringComparison.OrdinalIgnoreCase)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -136,7 +136,7 @@ namespace OpenIddict {
return; 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 // Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest
// rejects prompt=none requests missing or having an invalid id_token_hint. // rejects prompt=none requests missing or having an invalid id_token_hint.
@ -147,7 +147,7 @@ namespace OpenIddict {
// the initial check made by ValidateAuthorizationRequest. // the initial check made by ValidateAuthorizationRequest.
// In this case, ignore the prompt=none request and // In this case, ignore the prompt=none request and
// continue to the next middleware in the pipeline. // continue to the next middleware in the pipeline.
var user = await manager.GetUserAsync(principal); var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
return; return;
} }
@ -155,7 +155,7 @@ namespace OpenIddict {
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept // Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that
// don't include the "email" scope if the username corresponds to the registed email address. // 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); Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity. // 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 { namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class { public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { 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, // Note: OpenIdConnectServerHandler supports authorization code, refresh token,
// client credentials, resource owner password credentials and custom grants // 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. // 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) { if (application == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
@ -63,7 +63,7 @@ namespace OpenIddict {
} }
// Reject tokens requests containing a client_secret if the client application is not confidential. // 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( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Public clients are not allowed to send a client_secret."); description: "Public clients are not allowed to send a client_secret.");
@ -73,7 +73,7 @@ namespace OpenIddict {
// Confidential applications MUST authenticate // Confidential applications MUST authenticate
// to protect them from impersonation attacks. // to protect them from impersonation attacks.
else if (await manager.IsConfidentialApplicationAsync(application)) { else if (await services.Applications.IsConfidentialApplicationAsync(application)) {
if (string.IsNullOrEmpty(context.ClientSecret)) { if (string.IsNullOrEmpty(context.ClientSecret)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
@ -82,7 +82,7 @@ namespace OpenIddict {
return; return;
} }
if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) { if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret."); 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) { 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. // 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); Debug.Assert(application != null);
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
@ -107,7 +107,7 @@ namespace OpenIddict {
// access tokens, even if an explicit destination is not specified. // access tokens, even if an explicit destination is not specified.
identity.AddClaim(ClaimTypes.NameIdentifier, context.ClientId); 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.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken); OpenIdConnectConstants.Destinations.IdentityToken);
@ -125,13 +125,13 @@ namespace OpenIddict {
} }
public override async Task GrantRefreshToken([NotNull] GrantRefreshTokenContext context) { 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 options = context.HttpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>();
var principal = context.Ticket?.Principal; var principal = context.Ticket?.Principal;
Debug.Assert(principal != null); Debug.Assert(principal != null);
var user = await manager.GetUserAsync(principal); var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -142,10 +142,10 @@ namespace OpenIddict {
// If the user manager supports security stamps, // If the user manager supports security stamps,
// ensure that the refresh token is still valid. // ensure that the refresh token is still valid.
if (manager.SupportsUserSecurityStamp) { if (services.Users.SupportsUserSecurityStamp) {
var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType); var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType);
if (!string.IsNullOrEmpty(identifier) && 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( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid."); description: "The refresh token is no longer valid.");
@ -156,7 +156,7 @@ namespace OpenIddict {
// Note: the "scopes" property stored in context.AuthenticationTicket is automatically // Note: the "scopes" property stored in context.AuthenticationTicket is automatically
// updated by ASOS when the client application requests a restricted scopes collection. // 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); Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity but // Create a new authentication ticket holding the user identity but
@ -170,9 +170,9 @@ namespace OpenIddict {
} }
public override async Task GrantResourceOwnerCredentials([NotNull] GrantResourceOwnerCredentialsContext context) { 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) { if (user == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -182,7 +182,7 @@ namespace OpenIddict {
} }
// Ensure the user is not already locked out. // 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( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out."); description: "Account locked out.");
@ -191,16 +191,16 @@ namespace OpenIddict {
} }
// Ensure the password is valid. // Ensure the password is valid.
if (!await manager.CheckPasswordAsync(user, context.Password)) { if (!await services.Users.CheckPasswordAsync(user, context.Password)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials."); description: "Invalid credentials.");
if (manager.SupportsUserLockout) { if (services.Users.SupportsUserLockout) {
await manager.AccessFailedAsync(user); await services.Users.AccessFailedAsync(user);
// Ensure the user is not locked out. // Ensure the user is not locked out.
if (await manager.IsLockedOutAsync(user)) { if (await services.Users.IsLockedOutAsync(user)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant, error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out."); description: "Account locked out.");
@ -210,16 +210,16 @@ namespace OpenIddict {
return; return;
} }
if (manager.SupportsUserLockout) { if (services.Users.SupportsUserLockout) {
await manager.ResetAccessFailedCountAsync(user); await services.Users.ResetAccessFailedCountAsync(user);
} }
// Return an error if the username corresponds to the registered // Return an error if the username corresponds to the registered
// email address and if the "email" scope has not been requested. // email address and if the "email" scope has not been requested.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) &&
!context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) &&
string.Equals(await manager.GetUserNameAsync(user), string.Equals(await services.Users.GetUserNameAsync(user),
await manager.GetEmailAsync(user), await services.Users.GetEmailAsync(user),
StringComparison.OrdinalIgnoreCase)) { StringComparison.OrdinalIgnoreCase)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -228,7 +228,7 @@ namespace OpenIddict {
return; return;
} }
var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes()); var identity = await services.Applications.CreateIdentityAsync(user, context.Request.GetScopes());
Debug.Assert(identity != null); Debug.Assert(identity != null);
// Create a new authentication ticket holding the user identity. // 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 { namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class { public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { 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. // 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)) { 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. // 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) { if (application == null) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
@ -51,7 +51,7 @@ namespace OpenIddict {
} }
// Reject non-confidential applications. // Reject non-confidential applications.
if (await manager.IsPublicApplicationAsync(application)) { if (await services.Applications.IsPublicApplicationAsync(application)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Public applications are not allowed to use the introspection endpoint."); description: "Public applications are not allowed to use the introspection endpoint.");
@ -60,7 +60,7 @@ namespace OpenIddict {
} }
// Validate the client credentials. // Validate the client credentials.
if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) { if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret."); 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) { 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>>(); var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>();
// If the user manager doesn't support security // If the user manager doesn't support security
// stamps, skip the additional validation logic. // stamps, skip the additional validation logic.
if (!manager.SupportsUserSecurityStamp) { if (!services.Users.SupportsUserSecurityStamp) {
return; return;
} }
var principal = context.Ticket?.Principal; var principal = context.Ticket?.Principal;
Debug.Assert(principal != null); Debug.Assert(principal != null);
var user = await manager.GetUserAsync(principal); var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
context.Active = false; context.Active = false;
@ -93,7 +93,7 @@ namespace OpenIddict {
var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType); var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType);
if (!string.IsNullOrEmpty(identifier) && 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; context.Active = false;
return; return;

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

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

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

@ -16,14 +16,14 @@ using Newtonsoft.Json.Linq;
namespace OpenIddict { namespace OpenIddict {
public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class { public partial class OpenIddictProvider<TUser, TApplication> : OpenIdConnectServerProvider where TUser : class where TApplication : class {
public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) { 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; var principal = context.Ticket?.Principal;
Debug.Assert(principal != null); Debug.Assert(principal != null);
// Note: user may be null if the user has been removed. // Note: user may be null if the user has been removed.
// In this case, return a 400 response. // In this case, return a 400 response.
var user = await manager.GetUserAsync(principal); var user = await services.Users.GetUserAsync(principal);
if (user == null) { if (user == null) {
context.Response.StatusCode = 400; context.Response.StatusCode = 400;
context.HandleResponse(); context.HandleResponse();
@ -33,47 +33,47 @@ namespace OpenIddict {
// Note: "sub" is a mandatory claim. // Note: "sub" is a mandatory claim.
// See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse // 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. // 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 // Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that
// don't include the "email" scope if the username corresponds to the registed email address. // don't include the "email" scope if the username corresponds to the registed email address.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) { if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) {
context.PreferredUsername = await manager.GetUserNameAsync(user); context.PreferredUsername = await services.Users.GetUserNameAsync(user);
if (manager.SupportsUserClaim) { if (services.Users.SupportsUserClaim) {
context.FamilyName = await manager.FindClaimAsync(user, ClaimTypes.Surname); context.FamilyName = await services.Users.FindClaimAsync(user, ClaimTypes.Surname);
context.GivenName = await manager.FindClaimAsync(user, ClaimTypes.GivenName); context.GivenName = await services.Users.FindClaimAsync(user, ClaimTypes.GivenName);
context.BirthDate = await manager.FindClaimAsync(user, ClaimTypes.DateOfBirth); 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. // Only add the email address details if the "email" scope was present in the access token.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { 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 // Only add the "email_verified" claim
// if the email address is non-null. // if the email address is non-null.
if (!string.IsNullOrEmpty(context.Email)) { 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. // Only add the phone number details if the "phone" scope was present in the access token.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { 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" // Only add the "phone_number_verified"
// claim if the phone number is non-null. // claim if the phone number is non-null.
if (!string.IsNullOrEmpty(context.PhoneNumber)) { 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. // Only add the roles list if the "roles" scope was present in the access token.
if (manager.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) { if (services.Users.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) {
var roles = await manager.GetRolesAsync(user); var roles = await services.Users.GetRolesAsync(user);
if (roles.Count != 0) { if (roles.Count != 0) {
context.Claims[OpenIddictConstants.Claims.Roles] = JArray.FromObject(roles); context.Claims[OpenIddictConstants.Claims.Roles] = JArray.FromObject(roles);
} }

54
src/OpenIddict.Core/OpenIddictServices.cs

@ -5,32 +5,66 @@
*/ */
using System; using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict { namespace OpenIddict {
public class OpenIddictServices { /// <summary>
public OpenIddictServices(IServiceCollection services) { /// 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; Services = services;
} }
/// <summary> /// <summary>
/// Gets or sets the type corresponding to the Application entity. /// Gets the <see cref="OpenIddictManager{TUser, TApplication}"/>.
/// </summary> /// </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> /// <summary>
/// Gets or sets the type corresponding to the Role entity. /// Gets the <see cref="ILogger"/>.
/// </summary> /// </summary>
public Type RoleType { get; set; } public virtual ILogger Logger {
get { return Services.GetRequiredService<ILogger<OpenIddictManager<TUser, TApplication>>>(); }
}
/// <summary> /// <summary>
/// Gets or sets the type corresponding to the User entity. /// Gets the <see cref="IServiceProvider"/> used to resolve services.
/// </summary> /// </summary>
public Type UserType { get; set; } public virtual IServiceProvider Services { get; }
/// <summary> /// <summary>
/// Gets the services used by OpenIddict. /// Gets the <see cref="SignInManager{TUser}"/>.
/// </summary> /// </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.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OpenIddict; using OpenIddict;
using OpenIddict.Models;
namespace Microsoft.AspNetCore.Builder { namespace Microsoft.AspNetCore.Builder {
public static class OpenIddictExtensions { public static class OpenIddictExtensions {
public static OpenIddictServices UseEntityFramework([NotNull] this OpenIddictServices services) { public static OpenIddictConfiguration UseEntityFramework([NotNull] this OpenIddictConfiguration configuration) {
if (services == null) { if (configuration == null) {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(configuration));
} }
services.Services.AddScoped( if (!IsSubclassOf(configuration.ApplicationType, typeof(Application<>))) {
typeof(IOpenIddictStore<,>).MakeGenericType(services.UserType, services.ApplicationType), throw new InvalidOperationException("The default store cannot be used with application " +
typeof(OpenIddictStore<,,,,>).MakeGenericType( "entities that are not derived from Application<TKey>.");
/* TUser: */ services.UserType, }
/* TApplication: */ services.ApplicationType,
/* TRole: */ services.RoleType, configuration.Services.AddScoped(
/* TContext: */ ResolveContextType(services), typeof(IOpenIddictStore<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType),
/* TKey: */ ResolveKeyType(services))); 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) { private static Type ResolveContextType([NotNull] OpenIddictConfiguration configuration) {
var service = (from registration in services.Services var service = (from registration in configuration.Services
where registration.ServiceType.IsConstructedGenericType where registration.ServiceType.IsConstructedGenericType
let definition = registration.ServiceType.GetGenericTypeDefinition() let definition = registration.ServiceType.GetGenericTypeDefinition()
where definition == typeof(IUserStore<>) where definition == typeof(IUserStore<>)
select registration.ImplementationType).SingleOrDefault(); select registration.ImplementationType).FirstOrDefault();
if (service == null) { if (service == null) {
throw new InvalidOperationException( throw new InvalidOperationException(
@ -69,9 +74,9 @@ namespace Microsoft.AspNetCore.Builder {
throw new InvalidOperationException("The type of the database context cannot be automatically inferred."); 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; 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) { if (!type.IsGenericType) {
continue; continue;
} }
@ -90,7 +95,23 @@ namespace Microsoft.AspNetCore.Builder {
throw new InvalidOperationException( throw new InvalidOperationException(
"The type of the key identifier used by the user " + "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; using OpenIddict.Models;
namespace OpenIddict { 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 TUser : IdentityUser<TKey>
where TApplication : Application where TApplication : Application
where TRole : IdentityRole<TKey>
where TContext : DbContext where TContext : DbContext
where TKey : IEquatable<TKey> { where TKey : IEquatable<TKey> {
public OpenIddictStore(TContext context) public OpenIddictStore(TContext context) {
: base(context) { Context = context;
} }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
public virtual TContext Context { get; }
public DbSet<TApplication> Applications { public DbSet<TApplication> Applications {
get { return Context.Set<TApplication>(); } 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.Http.Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace OpenIddict.Mvc { namespace OpenIddict.Mvc {
// Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it. // 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 class OpenIddictController<TUser, TApplication> : Controller where TUser : class where TApplication : class {
public OpenIddictController( public OpenIddictController(
[NotNull] OpenIddictManager<TUser, TApplication> manager, [NotNull] OpenIddictServices<TUser, TApplication> services,
[NotNull] OpenIddictOptions options) { [NotNull] IOptions<OpenIddictOptions> options) {
Manager = manager; Services = services;
Options = options; Options = options.Value;
} }
/// <summary> /// <summary>
/// Gets the OpenIddict manager used by the controller. /// Gets the OpenIddict services used by the controller.
/// </summary> /// </summary>
protected virtual OpenIddictManager<TUser, TApplication> Manager { get; } protected virtual OpenIddictServices<TUser, TApplication> Services { get; }
/// <summary> /// <summary>
/// Gets the OpenIddict options used by the server. /// 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 // Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application
// in the authorization request by calling IOpenIdConnectServerProvider.ValidateAuthorizationRequest. // corresponds to the client_id specified in the authorization request using
// In theory, this null check shouldn't be needed, but a race condition could occur if you // IOpenIdConnectServerProvider.ValidateClientRedirectUri (see OpenIddictProvider.cs).
// manually removed the application details from the database after the initial check made by ASOS. var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId);
var application = await Manager.FindApplicationByIdAsync(request.ClientId);
if (application == null) { if (application == null) {
return View("Error", new OpenIdConnectMessage { return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.InvalidClient, 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] [Authorize, HttpPost, ValidateAntiForgeryToken]
@ -102,7 +102,7 @@ namespace OpenIddict.Mvc {
} }
// Retrieve the user data using the unique identifier. // Retrieve the user data using the unique identifier.
var user = await Manager.GetUserAsync(User); var user = await Services.Users.GetUserAsync(User);
if (user == null) { if (user == null) {
return View("Error", new OpenIdConnectMessage { return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.ServerError, Error = OpenIdConnectConstants.Errors.ServerError,
@ -112,17 +112,10 @@ namespace OpenIddict.Mvc {
// Create a new ClaimsIdentity containing the claims that // Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code. // 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); Debug.Assert(identity != null);
// Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId);
// 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.
if (application == null) { if (application == null) {
return View("Error", new OpenIdConnectMessage { return View("Error", new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.InvalidClient, Error = OpenIdConnectConstants.Errors.InvalidClient,
@ -136,7 +129,7 @@ namespace OpenIddict.Mvc {
identity.Actor = new ClaimsIdentity(Options.AuthenticationScheme); identity.Actor = new ClaimsIdentity(Options.AuthenticationScheme);
identity.Actor.AddClaim(ClaimTypes.NameIdentifier, request.ClientId); 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.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken); 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.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using OpenIddict; using OpenIddict;
using OpenIddict.Mvc; using OpenIddict.Mvc;
@ -49,12 +50,12 @@ namespace Microsoft.AspNetCore.Builder {
}); });
} }
}), services => { }), services => {
var registration = app.ApplicationServices.GetRequiredService<OpenIddictServices>(); var configuration = app.ApplicationServices.GetRequiredService<OpenIddictConfiguration>();
services.AddMvc() services.AddMvc()
// Register the OpenIddict controller. // Register the OpenIddict controller.
.AddControllersAsServices(new[] { .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 // Add an OpenIddict-specific convention to ensure that the generic
@ -69,30 +70,44 @@ namespace Microsoft.AspNetCore.Builder {
baseNamespace: typeof(OpenIddictController<,>).Namespace)); 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. // 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 accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null); Debug.Assert(container != null);
// Resolve the sign-in manager from the parent container. // 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. // 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 accessor = provider.GetRequiredService<IHttpContextAccessor>();
var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)];
Debug.Assert(container != null); Debug.Assert(container != null);
// Resolve the user manager from the parent container. // Resolve the user manager from the parent container.
return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(registration.UserType, registration.ApplicationType)); return container.GetRequiredService(typeof(UserManager<>).MakeGenericType(configuration.UserType));
});
// 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));
}); });
// Register the assembly provider in the isolated container. // Register the assembly provider in the isolated container.
@ -116,7 +131,7 @@ namespace Microsoft.AspNetCore.Builder {
}); });
// Register the options in the isolated container. // Register the options in the isolated container.
services.AddScoped(provider => builder.Options); services.AddSingleton(Options.Create(builder.Options));
})); }));
} }

Loading…
Cancel
Save