From d8fadc0f993fabef9270d10b8859f1450fb1ace9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 26 Sep 2016 17:27:44 +0200 Subject: [PATCH] Decouple OpenIddict from ASP.NET Core Identity --- .../Controllers/AccountController.cs | 42 ++++ .../Controllers/AuthorizationController.cs | 95 +++++---- samples/Mvc.Server/Startup.cs | 4 +- .../Infrastructure/OpenIddictHelpers.cs | 32 --- .../OpenIddictProvider.Authentication.cs | 64 +----- .../OpenIddictProvider.Discovery.cs | 27 +-- .../OpenIddictProvider.Exchange.cs | 54 +---- .../OpenIddictProvider.Introspection.cs | 20 +- .../OpenIddictProvider.Revocation.cs | 9 +- .../OpenIddictProvider.Serialization.cs | 67 +----- .../OpenIddictProvider.Session.cs | 12 +- .../OpenIddictProvider.Userinfo.cs | 78 +------ .../Infrastructure/OpenIddictProvider.cs | 4 +- .../Infrastructure/OpenIddictServices.cs | 17 +- .../Managers/OpenIddictTokenManager.cs | 9 - .../Managers/OpenIddictUserManager.cs | 192 ------------------ src/OpenIddict.Core/OpenIddictBuilder.cs | 44 ---- src/OpenIddict.Core/OpenIddictExtensions.cs | 21 +- .../Stores/IOpenIddictUserStore.cs | 55 ----- src/OpenIddict.Core/project.json | 3 +- .../OpenIddictExtensions.cs | 16 +- .../Stores/OpenIddictTokenStore.cs | 4 +- .../Stores/OpenIddictUserStore.cs | 162 --------------- src/OpenIddict.EntityFramework/project.json | 2 +- src/OpenIddict.Mvc/project.json | 2 +- src/OpenIddict/OpenIddictExtensions.cs | 102 +--------- src/OpenIddict/project.json | 2 +- 27 files changed, 176 insertions(+), 963 deletions(-) delete mode 100644 src/OpenIddict.Core/Managers/OpenIddictUserManager.cs delete mode 100644 src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs delete mode 100644 src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs diff --git a/samples/Mvc.Server/Controllers/AccountController.cs b/samples/Mvc.Server/Controllers/AccountController.cs index 5186942c..3b580a51 100644 --- a/samples/Mvc.Server/Controllers/AccountController.cs +++ b/samples/Mvc.Server/Controllers/AccountController.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using AspNet.Security.OAuth.Validation; +using AspNet.Security.OpenIdConnect.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -8,6 +10,8 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Mvc.Server.Models; using Mvc.Server.Services; using Mvc.Server.ViewModels.Account; +using Newtonsoft.Json.Linq; +using OpenIddict; namespace Mvc.Server.Controllers { [Authorize] @@ -32,6 +36,44 @@ namespace Mvc.Server.Controllers { _applicationDbContext = applicationDbContext; } + // + // GET: /Account/Userinfo + [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] + [HttpGet, Produces("application/json")] + public async Task Userinfo() { + var user = await _userManager.GetUserAsync(User); + if (user == null) { + return BadRequest(new OpenIdConnectResponse { + Error = OpenIdConnectConstants.Errors.InvalidGrant, + ErrorDescription = "The user profile is no longer available." + }); + } + + var claims = new JObject(); + + // Note: the "sub" claim is a mandatory claim and must be included in the JSON response. + claims[OpenIdConnectConstants.Claims.Subject] = user.Id.ToString(); + + if (User.HasClaim(OpenIdConnectConstants.Claims.Scope, OpenIdConnectConstants.Scopes.Email)) { + claims[OpenIdConnectConstants.Claims.Email] = user.Email; + claims[OpenIdConnectConstants.Claims.EmailVerified] = user.EmailConfirmed; + } + + if (User.HasClaim(OpenIdConnectConstants.Claims.Scope, OpenIdConnectConstants.Scopes.Phone)) { + claims[OpenIdConnectConstants.Claims.PhoneNumber] = user.PhoneNumber; + claims[OpenIdConnectConstants.Claims.PhoneNumberVerified] = user.PhoneNumberConfirmed; + } + + if (User.HasClaim(OpenIdConnectConstants.Claims.Scope, OpenIddictConstants.Scopes.Roles)) { + claims[OpenIddictConstants.Claims.Roles] = JArray.FromObject(await _userManager.GetRolesAsync(user)); + } + + // Note: the complete list of standard claims supported by the OpenID Connect specification + // can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + + return Json(claims); + } + // // GET: /Account/Login [HttpGet] diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index ea4bcc61..e5b11955 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs @@ -24,12 +24,12 @@ namespace Mvc.Server { public class AuthorizationController : Controller { private readonly OpenIddictApplicationManager> _applicationManager; private readonly SignInManager _signInManager; - private readonly OpenIddictUserManager _userManager; + private readonly UserManager _userManager; public AuthorizationController( OpenIddictApplicationManager> applicationManager, SignInManager signInManager, - OpenIddictUserManager userManager) { + UserManager userManager) { _applicationManager = applicationManager; _signInManager = signInManager; _userManager = userManager; @@ -69,23 +69,8 @@ namespace Mvc.Server { }); } - // Create a new ClaimsIdentity containing the claims that - // will be used to create an id_token, a token or a code. - var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes()); - - // Create a new authentication ticket holding the user identity. - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(identity), - new AuthenticationProperties(), - OpenIdConnectServerDefaults.AuthenticationScheme); - - // Set the list of scopes granted to the client application. - ticket.SetScopes(new[] { - /* openid: */ OpenIdConnectConstants.Scopes.OpenId, - /* email: */ OpenIdConnectConstants.Scopes.Email, - /* profile: */ OpenIdConnectConstants.Scopes.Profile, - /* offline_access: */ OpenIdConnectConstants.Scopes.OfflineAccess - }.Intersect(request.GetScopes())); + // Create a new authentication ticket. + var ticket = await CreateTicketAsync(request, user); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); @@ -98,6 +83,9 @@ namespace Mvc.Server { return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme); } + // Note: the logout action is only useful when implementing interactive + // flows like the authorization code flow or the implicit flow. + [HttpGet("~/connect/logout")] public IActionResult Logout(OpenIdConnectRequest request) { // Flow the request_id to allow OpenIddict to restore @@ -174,21 +162,8 @@ namespace Mvc.Server { await _userManager.ResetAccessFailedCountAsync(user); } - var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes()); - - // Create a new authentication ticket holding the user identity. - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(identity), - new AuthenticationProperties(), - OpenIdConnectServerDefaults.AuthenticationScheme); - - // Set the list of scopes granted to the client application. - ticket.SetScopes(new[] { - /* openid: */ OpenIdConnectConstants.Scopes.OpenId, - /* email: */ OpenIdConnectConstants.Scopes.Email, - /* profile: */ OpenIdConnectConstants.Scopes.Profile, - /* offline_access: */ OpenIdConnectConstants.Scopes.OfflineAccess - }.Intersect(request.GetScopes())); + // Create a new authentication ticket. + var ticket = await CreateTicketAsync(request, user); return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); } @@ -198,5 +173,57 @@ namespace Mvc.Server { ErrorDescription = "The specified grant type is not supported." }); } + + private async Task CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) { + // Set the list of scopes granted to the client application. + // Note: the offline_access scope must be granted + // to allow OpenIddict to return a refresh token. + var scopes = new[] { + OpenIdConnectConstants.Scopes.OpenId, + OpenIdConnectConstants.Scopes.Email, + OpenIdConnectConstants.Scopes.Profile, + OpenIdConnectConstants.Scopes.OfflineAccess + }.Intersect(request.GetScopes()); + + // Create a new ClaimsPrincipal containing the claims that + // will be used to create an id_token, a token or a code. + var principal = await _signInManager.CreateUserPrincipalAsync(user); + + // Note: by default, claims are NOT automatically included in the access and identity tokens. + // To allow OpenIddict to serialize them, you must attach them a destination, that specifies + // whether they should be included in access tokens, in identity tokens or in both. + + foreach (var claim in principal.Claims) { + // Always include the user identifier in the + // access token and the identity token. + if (claim.Type == ClaimTypes.NameIdentifier) { + claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + + // Include the name claim, but only if the "profile" scope was requested. + else if (claim.Type == ClaimTypes.Name && scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { + claim.SetDestinations(OpenIdConnectConstants.Destinations.IdentityToken); + } + + // Include the role claims, but only if the "roles" scope was requested. + else if (claim.Type == ClaimTypes.Role && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { + claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, + OpenIdConnectConstants.Destinations.IdentityToken); + } + + // The other claims won't be added to the access + // and identity tokens and will be kept private. + } + + // Create a new authentication ticket holding the user identity. + var ticket = new AuthenticationTicket( + principal, new AuthenticationProperties(), + OpenIdConnectServerDefaults.AuthenticationScheme); + + ticket.SetScopes(scopes); + + return ticket; + } } } \ No newline at end of file diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 4aeb6a95..fb445f99 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -30,7 +30,7 @@ namespace Mvc.Server { .AddDefaultTokenProviders(); // Register the OpenIddict services, including the default Entity Framework stores. - services.AddOpenIddict, ApplicationDbContext, Guid>() + services.AddOpenIddict() // Register the ASP.NET Core MVC binder used by OpenIddict. // Note: if you don't call this method, you won't be able to // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. @@ -40,7 +40,7 @@ namespace Mvc.Server { .EnableAuthorizationEndpoint("/connect/authorize") .EnableLogoutEndpoint("/connect/logout") .EnableTokenEndpoint("/connect/token") - .EnableUserinfoEndpoint("/connect/userinfo") + .EnableUserinfoEndpoint("/Account/Userinfo") // Note: the Mvc.Client sample only uses the code flow and the password flow, but you // can enable the other flows if you need to support implicit or client credentials. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs index fa7a628f..3192174c 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs @@ -5,43 +5,11 @@ */ using System; -using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; -using Microsoft.AspNetCore.Identity; namespace OpenIddict.Infrastructure { public static class OpenIddictHelpers { - /// - /// Tries to find the given claim in the user claims. - /// - /// The type of the User entity. - /// The user manager. - /// The user. - /// The claim type. - /// The claim value, or null if it cannot be found. - public static async Task FindClaimAsync( - [NotNull] this UserManager 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. - return (from claim in await manager.GetClaimsAsync(user) - where string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase) - select claim.Value).FirstOrDefault(); - } - /// /// Determines whether an application is a confidential client. /// diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs index e391de64..e6585c7c 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs @@ -5,7 +5,6 @@ */ using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Claims; @@ -13,10 +12,8 @@ using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Http.Authentication; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; @@ -25,10 +22,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Bson; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ExtractAuthorizationRequest([NotNull] ExtractAuthorizationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Reject requests using the unsupported request parameter. if (!string.IsNullOrEmpty(context.Request.Request)) { @@ -86,7 +83,7 @@ namespace OpenIddict.Infrastructure { } public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: the OpenID Connect server middleware supports authorization code, implicit, hybrid, // none and custom flows but OpenIddict uses a stricter policy rejecting unknown flows. @@ -311,56 +308,7 @@ namespace OpenIddict.Infrastructure { } public override async Task HandleAuthorizationRequest([NotNull] HandleAuthorizationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); - - if (string.Equals(context.Request.Prompt, "none", StringComparison.Ordinal)) { - // Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest - // rejects prompt=none requests missing or having an invalid id_token_hint. - var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme); - Debug.Assert(principal != null, "The principal extracted from the id_token_hint shouldn't be null."); - - // Note: user may be null if the user was removed after - // the initial check made by ValidateAuthorizationRequest. - var user = await services.Users.GetUserAsync(principal); - if (user == null) { - services.Logger.LogError("The authorization request was aborted because the profile corresponding " + - "to the logged in user was not found in the database: {Identifier}.", - context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier)); - - context.Reject( - error: OpenIdConnectConstants.Errors.ServerError, - description: "An internal error has occurred."); - - return; - } - - // Note: filtering the username is not needed at this stage as OpenIddictController.Accept - // and OpenIddictProvider.HandleTokenRequest are expected to reject requests that don't - // include the "email" scope if the username corresponds to the registed email address. - var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes()); - if (identity == null) { - throw new InvalidOperationException("The authorization request was aborted because the user manager returned a null " + - $"identity for user '{await services.Users.GetUserNameAsync(user)}'."); - } - - // Create a new authentication ticket holding the user identity. - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(identity), - new AuthenticationProperties(), - context.Options.AuthenticationScheme); - - ticket.SetResources(context.Request.GetResources()); - ticket.SetScopes(context.Request.GetScopes()); - - // Call SignInAsync to create and return a new OpenID Connect response containing the serialized code/tokens. - await context.HttpContext.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties); - - // Mark the response as handled - // to skip the rest of the pipeline. - context.HandleResponse(); - - return; - } + var services = context.HttpContext.RequestServices.GetRequiredService>(); // If no request_id parameter can be found in the current request, assume the OpenID Connect request // was not serialized yet and store the entire payload in the distributed cache to make it easier @@ -406,7 +354,7 @@ namespace OpenIddict.Infrastructure { } public override async Task ApplyAuthorizationResponse([NotNull] ApplyAuthorizationResponseContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Remove the authorization request from the distributed cache. if (!string.IsNullOrEmpty(context.Request.RequestId)) { diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs index b86333fd..fd688f41 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs @@ -11,10 +11,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task HandleConfigurationRequest([NotNull] HandleConfigurationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: though it's natively supported by the OpenID Connect server middleware, // OpenIddict disallows the use of the unsecure code_challenge_method=plain method, @@ -35,24 +35,9 @@ namespace OpenIddict.Infrastructure { // Note: the "openid" scope is automatically // added by the OpenID Connect server middleware. context.Scopes.Add(OpenIdConnectConstants.Scopes.Profile); - - // Only add the "email" scope if it's supported - // by the user manager and the underlying store. - if (services.Users.SupportsUserEmail) { - context.Scopes.Add(OpenIdConnectConstants.Scopes.Email); - } - - // Only add the "phone" scope if it's supported - // by the user manager and the underlying store. - if (services.Users.SupportsUserPhoneNumber) { - context.Scopes.Add(OpenIdConnectConstants.Scopes.Phone); - } - - // Only add the "roles" scope if it's supported - // by the user manager and the underlying store. - if (services.Users.SupportsUserRole) { - context.Scopes.Add(OpenIddictConstants.Scopes.Roles); - } + context.Scopes.Add(OpenIdConnectConstants.Scopes.Email); + context.Scopes.Add(OpenIdConnectConstants.Scopes.Phone); + context.Scopes.Add(OpenIddictConstants.Scopes.Roles); // Only add the "offline_access" scope if "refresh_token" is listed as a supported grant type. if (context.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs index b1453ae3..dd4eeeb3 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs @@ -4,23 +4,20 @@ * the license and the contributors participating to this project. */ -using System; using System.Diagnostics; -using System.Security.Claims; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Reject token requests that don't specify a supported grant type. if (!services.Options.GrantTypes.Contains(context.Request.GrantType)) { @@ -180,7 +177,7 @@ namespace OpenIddict.Infrastructure { } public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: the OpenID Connect server middleware automatically reuses the authentication ticket // stored in the authorization code to create a new identity. To ensure the user was not removed @@ -188,19 +185,6 @@ namespace OpenIddict.Infrastructure { if (context.Request.IsAuthorizationCodeGrantType()) { Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); - var user = await services.Users.GetUserAsync(context.Ticket.Principal); - if (user == null) { - services.Logger.LogError("The token request was rejected because the user profile associated " + - "with the authorization code was not found in the database: '{Identifier}'.", - context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier)); - - context.Reject( - error: OpenIdConnectConstants.Errors.InvalidGrant, - description: "The authorization code is no longer valid."); - - return; - } - // Extract the token identifier from the authorization code. var identifier = context.Ticket.GetTicketId(); Debug.Assert(!string.IsNullOrEmpty(identifier), @@ -232,19 +216,6 @@ namespace OpenIddict.Infrastructure { else if (context.Request.IsRefreshTokenGrantType()) { Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); - var user = await services.Users.GetUserAsync(context.Ticket.Principal); - if (user == null) { - services.Logger.LogError("The token request was rejected because the user profile associated " + - "with the refresh token was not found in the database: '{Identifier}'.", - context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier)); - - context.Reject( - error: OpenIdConnectConstants.Errors.InvalidGrant, - description: "The refresh token is no longer valid."); - - return; - } - // Extract the token identifier from the refresh token. var identifier = context.Ticket.GetTicketId(); Debug.Assert(!string.IsNullOrEmpty(identifier), @@ -269,22 +240,7 @@ namespace OpenIddict.Infrastructure { await services.Tokens.RevokeAsync(token); } - // Note: the "scopes" property stored in context.AuthenticationTicket is automatically updated by the - // OpenID Connect server middleware when the client application requests a restricted scopes collection. - var identity = await services.Users.CreateIdentityAsync(user, context.Ticket.GetScopes()); - if (identity == null) { - throw new InvalidOperationException("The token request was aborted because the user manager returned a null " + - $"identity for user '{await services.Users.GetUserNameAsync(user)}'."); - } - - // Create a new authentication ticket holding the user identity but - // reuse the authentication properties stored in the refresh token. - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(identity), - context.Ticket.Properties, - context.Options.AuthenticationScheme); - - context.Validate(ticket); + context.Validate(context.Ticket); return; } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs index fd0d0693..db4b0ba8 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs @@ -14,10 +14,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: the OpenID Connect server middleware supports both GET and POST // introspection requests but OpenIddict only accepts POST requests. @@ -82,7 +82,7 @@ namespace OpenIddict.Infrastructure { } public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), "The client_id parameter shouldn't be null."); @@ -101,18 +101,6 @@ namespace OpenIddict.Infrastructure { return; } - var user = await services.Users.GetUserAsync(context.Ticket.Principal); - if (user == null) { - services.Logger.LogInformation("The token {Identifier} was declared as inactive because the " + - "corresponding user ({Username}) was not found in the database.", - context.Ticket.GetTicketId(), services.Users.GetUserName(context.Ticket.Principal)); - - context.Claims.RemoveAll(); - context.Active = false; - - return; - } - // When the received ticket is revocable, ensure it is still valid. if (context.Ticket.IsAuthorizationCode() || context.Ticket.IsRefreshToken()) { // Retrieve the token from the database using the unique identifier stored in the authentication ticket: diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs index c0da2164..d0fa4a92 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs @@ -4,7 +4,6 @@ * the license and the contributors participating to this project. */ -using System; using System.Diagnostics; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; @@ -14,10 +13,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ValidateRevocationRequest([NotNull] ValidateRevocationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token. if (!string.IsNullOrEmpty(context.Request.TokenTypeHint) && @@ -110,7 +109,7 @@ namespace OpenIddict.Infrastructure { } public override async Task HandleRevocationRequest([NotNull] HandleRevocationRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs index d8148c6c..1f0d09d1 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs @@ -5,8 +5,6 @@ */ using System; -using System.Diagnostics; -using System.Security.Claims; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; @@ -14,31 +12,12 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); - - Debug.Assert(context.Request.IsAuthorizationRequest(), "The request should be an authorization request."); - Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), "The client_id parameter shouldn't be null or empty."); - - // Note: a null value could be returned by FindByIdAsync. In this case, throw an exception to abort the token request. - var user = await services.Users.FindByIdAsync(context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier)); - if (user == null) { - throw new InvalidOperationException("The token request was aborted because the user associated " + - "with the authorization code was not found in the database."); - } - - var application = await services.Applications.FindByClientIdAsync(context.Request.ClientId); - if (application == null) { - throw new InvalidOperationException("The application cannot be retrieved from the database."); - } - - // Persist a new token entry in the database and attach it - // to the user and the client application it is issued to. - var identifier = await services.Users.CreateTokenAsync(user, context.Request.ClientId, - OpenIdConnectConstants.TokenTypeHints.AuthorizationCode); + var services = context.HttpContext.RequestServices.GetRequiredService>(); + var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty."); } @@ -50,43 +29,9 @@ namespace OpenIddict.Infrastructure { } public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); - - Debug.Assert(context.Request.IsTokenRequest(), "The request should be a token request."); - Debug.Assert(!context.Request.IsClientCredentialsGrantType(), - "A refresh token should not be issued when using grant_type=client_credentials."); - - // Note: a null value could be returned by FindByIdAsync if the user was removed after the initial - // check made by HandleTokenRequest. In this case, throw an exception to abort the token request. - var user = await services.Users.FindByIdAsync(context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier)); - if (user == null) { - throw new InvalidOperationException("The token request was aborted because the user associated " + - "with the refresh token was not found in the database."); - } - - string identifier; - - // If the client application sending the token request is known, - // ensure the token is attached to the corresponding client entity. - if (!string.IsNullOrEmpty(context.Request.ClientId)) { - var application = await services.Applications.FindByClientIdAsync(context.Request.ClientId); - if (application == null) { - throw new InvalidOperationException("The application cannot be retrieved from the database."); - } - - // Persist a new token entry in the database and attach it - // to the user and the client application it is issued to. - identifier = await services.Users.CreateTokenAsync(user, context.Request.ClientId, - OpenIdConnectConstants.TokenTypeHints.RefreshToken); - } - - else { - // Persist a new token entry in the database - // and attach it to the user it corresponds to. - identifier = await services.Users.CreateTokenAsync(user, - OpenIdConnectConstants.TokenTypeHints.RefreshToken); - } + var services = context.HttpContext.RequestServices.GetRequiredService>(); + var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs index df028ab4..089f91ad 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs @@ -19,10 +19,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Bson; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ExtractLogoutRequest([NotNull] ExtractLogoutRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // If a request_id parameter can be found in the logout request, // restore the complete logout request stored in the distributed cache. @@ -56,7 +56,7 @@ namespace OpenIddict.Infrastructure { } public override async Task ValidateLogoutRequest([NotNull] ValidateLogoutRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Skip validation if the optional post_logout_redirect_uri // parameter was missing from the logout request. @@ -86,7 +86,7 @@ namespace OpenIddict.Infrastructure { } public override async Task HandleLogoutRequest([NotNull] HandleLogoutRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // If no request_id parameter can be found in the current request, assume the OpenID Connect // request was not serialized yet and store the entire payload in the distributed cache @@ -130,7 +130,7 @@ namespace OpenIddict.Infrastructure { } public override async Task ApplyLogoutResponse([NotNull] ApplyLogoutResponseContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Remove the logout request from the distributed cache. if (!string.IsNullOrEmpty(context.Request.RequestId)) { diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs index a2c9b1ca..40097aa7 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs @@ -4,81 +4,19 @@ * the license and the contributors participating to this project. */ -using System.Security.Claims; using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { - public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) { - var services = context.HttpContext.RequestServices.GetRequiredService>(); - - // Note: user may be null if the user was removed after the access token was issued. - var user = await services.Users.GetUserAsync(context.Ticket.Principal); - if (user == null) { - services.Logger.LogError("The userinfo request was aborted because the user profile " + - "corresponding to the access token was not found in the database."); - - context.Reject( - error: OpenIdConnectConstants.Errors.InvalidGrant, - description: "The access token is no longer valid."); - - return; - } - - // Note: "sub" is a mandatory claim. - // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse - 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.HandleTokenRequest 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 services.Users.GetUserNameAsync(user); - - 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 (services.Users.SupportsUserEmail && context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { - 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 services.Users.IsEmailConfirmedAsync(user); - } - }; - - // Only add the phone number details if the "phone" scope was present in the access token. - if (services.Users.SupportsUserPhoneNumber && context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { - 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 services.Users.IsPhoneNumberConfirmedAsync(user); - } - } - - // Only add the roles list if the "roles" scope was present in the access token. - 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); - } - } + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public override Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) { + // Invoke the rest of the pipeline to allow + // the user code to handle the userinfo request. + context.SkipToNextMiddleware(); + + return Task.FromResult(0); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs index 1f943898..4847dc32 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs @@ -9,8 +9,8 @@ using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; namespace OpenIddict.Infrastructure { - public partial class OpenIddictProvider : OpenIdConnectServerProvider - where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class { + public partial class OpenIddictProvider : OpenIdConnectServerProvider + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task MatchEndpoint([NotNull] MatchEndpointContext context) { // Note: by default, OpenIdConnectServerHandler only handles authorization requests made to AuthorizationEndpointPath. // This context handler uses a more relaxed policy that allows extracting authorization requests received at diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs index 40d69379..f683dd08 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs +++ b/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs @@ -7,7 +7,6 @@ using System; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -16,8 +15,8 @@ namespace OpenIddict.Infrastructure { /// /// Exposes the common services used by OpenIddict. /// - public class OpenIddictServices - where TUser : class where TApplication : class where TAuthorization : class + public class OpenIddictServices + where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public OpenIddictServices([NotNull] IServiceProvider services) { Services = services; @@ -38,7 +37,7 @@ namespace OpenIddict.Infrastructure { /// Gets the . /// public virtual ILogger Logger => - Services.GetRequiredService>>(); + Services.GetRequiredService>>(); /// /// Gets the . @@ -50,19 +49,9 @@ namespace OpenIddict.Infrastructure { /// public virtual IServiceProvider Services { get; } - /// - /// Gets the . - /// - public virtual SignInManager SignIn => Services.GetRequiredService>(); - /// /// Gets the . /// public virtual OpenIddictTokenManager Tokens => Services.GetRequiredService>(); - - /// - /// Gets the . - /// - public virtual OpenIddictUserManager Users => Services.GetRequiredService>(); } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 696a8602..a749b41d 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -8,11 +8,9 @@ using System; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace OpenIddict { /// @@ -23,11 +21,9 @@ namespace OpenIddict { public OpenIddictTokenManager( [NotNull] IServiceProvider services, [NotNull] IOpenIddictTokenStore store, - [NotNull] IOptions options, [NotNull] ILogger> logger) { Context = services?.GetRequiredService()?.HttpContext; Logger = logger; - Options = options.Value; Store = store; } @@ -46,11 +42,6 @@ namespace OpenIddict { /// protected ILogger Logger { get; } - /// - /// Gets the identity options. - /// - protected IdentityOptions Options { get; } - /// /// Gets the store associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs b/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs deleted file mode 100644 index 42a2a36e..00000000 --- a/src/OpenIddict.Core/Managers/OpenIddictUserManager.cs +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Extensions; -using AspNet.Security.OpenIdConnect.Server; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace OpenIddict { - /// - /// Provides methods allowing to manage the users stored in the store. - /// - /// The type of the User entity. - public class OpenIddictUserManager : UserManager where TUser : class { - public OpenIddictUserManager( - [NotNull] IServiceProvider services, - [NotNull] IOpenIddictUserStore store, - [NotNull] IOptions options, - [NotNull] ILogger> logger, - [NotNull] IPasswordHasher hasher, - [NotNull] IEnumerable> userValidators, - [NotNull] IEnumerable> passwordValidators, - [NotNull] ILookupNormalizer keyNormalizer, - [NotNull] IdentityErrorDescriber errors) - : base(store, options, hasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { - Context = services?.GetRequiredService()?.HttpContext; - Logger = logger; - Options = options.Value; - Store = store; - } - - /// - /// Gets the cancellation token used to abort async operations. - /// - protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; - - /// - /// Gets the HTTP context associated with the current manager. - /// - protected HttpContext Context { get; } - - /// - /// Gets the identity options. - /// - protected IdentityOptions Options { get; } - - /// - /// Gets the store associated with the current manager. - /// - protected new IOpenIddictUserStore Store { get; } - - /// - /// Creates a new used to create new tokens. - /// - /// The user corresponding to the identity. - /// The scopes granted by the resource owner. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the corresponding to the user. - /// - public virtual async Task CreateIdentityAsync(TUser user, IEnumerable scopes) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (scopes == null) { - throw new ArgumentNullException(nameof(scopes)); - } - - var identity = new ClaimsIdentity( - OpenIdConnectServerDefaults.AuthenticationScheme, - Options.ClaimsIdentity.UserNameClaimType, - Options.ClaimsIdentity.RoleClaimType); - - // Note: the name identifier is always included in both identity and - // access tokens, even if an explicit destination is not specified. - identity.AddClaim(ClaimTypes.NameIdentifier, await GetUserIdAsync(user)); - - // Only add the name claim if the "profile" scope was granted. - if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { - var username = await GetUserNameAsync(user); - - identity.AddClaim(ClaimTypes.Name, username, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - - // Only add the email address if the "email" scope was granted. - if (SupportsUserEmail && scopes.Contains(OpenIdConnectConstants.Scopes.Email)) { - var email = await GetEmailAsync(user); - - identity.AddClaim(ClaimTypes.Email, email, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - - if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { - foreach (var role in await GetRolesAsync(user)) { - identity.AddClaim(identity.RoleClaimType, role, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - } - - if (SupportsUserSecurityStamp) { - var stamp = await GetSecurityStampAsync(user); - - if (!string.IsNullOrEmpty(stamp)) { - identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, stamp, - OpenIdConnectConstants.Destinations.AccessToken, - OpenIdConnectConstants.Destinations.IdentityToken); - } - } - - return identity; - } - - /// - /// Creates a new token associated with the given user. - /// - /// The user. - /// The token type. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - public virtual Task CreateTokenAsync(TUser user, string type) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); - } - - return Store.CreateTokenAsync(user, type, CancellationToken); - } - - /// - /// Creates a new token associated with the given user and - /// attached to the tokens issued to the specified client. - /// - /// The user. - /// The application. - /// The token type. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - public virtual Task CreateTokenAsync(TUser user, string client, string type) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); - } - - return Store.CreateTokenAsync(user, client, type, CancellationToken); - } - - /// - /// Retrieves the token identifiers associated with a user. - /// - /// The user. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens associated with the user. - /// - public virtual Task> GetTokensAsync(TUser user) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - return Store.GetTokensAsync(user, CancellationToken); - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index 60886652..d7c065c3 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -44,12 +44,6 @@ namespace Microsoft.AspNetCore.Builder { [EditorBrowsable(EditorBrowsableState.Never)] public Type AuthorizationType { get; set; } - /// - /// Gets or sets the type corresponding to the Role entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type RoleType { get; set; } - /// /// Gets or sets the type corresponding to the Scope entity. /// @@ -62,12 +56,6 @@ namespace Microsoft.AspNetCore.Builder { [EditorBrowsable(EditorBrowsableState.Never)] public Type TokenType { get; set; } - /// - /// Gets or sets the type corresponding to the User entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type UserType { get; set; } - /// /// Gets the services collection. /// @@ -218,38 +206,6 @@ namespace Microsoft.AspNetCore.Builder { return this; } - /// - /// Adds a custom user manager. - /// - /// The type of the custom manager. - /// The . - public virtual OpenIddictBuilder AddUserManager() { - var contract = typeof(OpenIddictUserManager<>).MakeGenericType(UserType); - if (!contract.IsAssignableFrom(typeof(TManager))) { - throw new InvalidOperationException("Custom managers must be derived from OpenIddictUserManager."); - } - - Services.AddScoped(contract, typeof(TManager)); - - return this; - } - - /// - /// Adds a custom user store. - /// - /// The type of the custom store. - /// The . - public virtual OpenIddictBuilder AddUserStore() { - var contract = typeof(IOpenIddictUserStore<>).MakeGenericType(UserType); - if (!contract.IsAssignableFrom(typeof(TStore))) { - throw new InvalidOperationException("Custom stores must implement IOpenIddictUserStore."); - } - - Services.AddScoped(contract, typeof(TStore)); - - return this; - } - /// /// Registers a new OpenIddict module. If a module with the same name already /// exists, the new instance is ignored and this extension has no effect. diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 0b24736b..84cb24ea 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -21,24 +21,14 @@ namespace Microsoft.AspNetCore.Builder { /// Registers the OpenIddict core services in the DI container. /// When using this method, custom stores must be manually registered. /// - /// The type of the User entity. - /// The type of the Role entity. /// The type of the Application entity. /// The type of the Authorization entity. /// The type of the Scope entity. /// The type of the Token entity. /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// /// The . - public static OpenIddictBuilder AddOpenIddict( + public static OpenIddictBuilder AddOpenIddict( [NotNull] this IServiceCollection services) - where TUser : class - where TRole : class where TApplication : class where TAuthorization : class where TScope : class @@ -50,10 +40,8 @@ namespace Microsoft.AspNetCore.Builder { var builder = new OpenIddictBuilder(services) { ApplicationType = typeof(TApplication), AuthorizationType = typeof(TAuthorization), - RoleType = typeof(TRole), ScopeType = typeof(TScope), - TokenType = typeof(TToken), - UserType = typeof(TUser) + TokenType = typeof(TToken) }; // Register the services required by the OpenID Connect server middleware. @@ -62,7 +50,7 @@ namespace Microsoft.AspNetCore.Builder { builder.Configure(options => { // Register the OpenID Connect server provider in the OpenIddict options. - options.Provider = new OpenIddictProvider(); + options.Provider = new OpenIddictProvider(); }); // Register the OpenIddict core services in the DI container. @@ -70,8 +58,7 @@ namespace Microsoft.AspNetCore.Builder { builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); + builder.Services.TryAddScoped>(); return builder; } diff --git a/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs deleted file mode 100644 index 39fcd333..00000000 --- a/src/OpenIddict.Core/Stores/IOpenIddictUserStore.cs +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; - -namespace OpenIddict { - /// - /// Provides methods allowing to manage the users stored in a database. - /// - /// The type of the User entity. - public interface IOpenIddictUserStore : IUserStore where TUser : class { - /// - /// Creates a new token associated with the given user. - /// - /// The user associated with the token. - /// The token type. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - Task CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken); - - /// - /// Creates a new token associated with the given user and - /// attached to the tokens issued to the specified client. - /// - /// The user. - /// The application. - /// The token type. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - Task CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken); - - /// - /// Retrieves the token identifiers associated with a user. - /// - /// The user. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens associated with the user. - /// - Task> GetTokensAsync(TUser user, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/OpenIddict.Core/project.json b/src/OpenIddict.Core/project.json index 16468f52..a513ecb0 100644 --- a/src/OpenIddict.Core/project.json +++ b/src/OpenIddict.Core/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha2-*", + "version": "1.0.0-beta1-*", "description": "Core components of OpenIddict.", "authors": [ "Kévin Chalet" ], @@ -37,7 +37,6 @@ "CryptoHelper": "2.0.0", "JetBrains.Annotations": { "type": "build", "version": "10.1.4" }, "Microsoft.AspNetCore.Diagnostics.Abstractions": "1.0.0", - "Microsoft.AspNetCore.Identity": "1.0.0", "Microsoft.Extensions.Caching.Memory": "1.0.0" }, diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index 0b65a4dc..4af8efb2 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -37,7 +37,6 @@ namespace Microsoft.AspNetCore.Builder { Debug.Assert(builder.ApplicationType != null && builder.AuthorizationType != null && - builder.RoleType != null && builder.ScopeType != null && builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null."); @@ -70,22 +69,9 @@ namespace Microsoft.AspNetCore.Builder { // Register the token store in the DI container. builder.Services.TryAddScoped( typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType), - typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( + typeof(OpenIddictTokenStore<,,,>).MakeGenericType( /* TToken: */ builder.TokenType, /* TAuthorization: */ builder.AuthorizationType, - /* TUser: */ builder.UserType, - /* TContext: */ typeof(TContext), - /* TKey: */ typeof(TKey))); - - // Register the token store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictUserStore<>).MakeGenericType(builder.UserType), - typeof(OpenIddictUserStore<,,,,,,>).MakeGenericType( - /* TUser: */ builder.UserType, - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TRole: */ builder.RoleType, - /* TToken: */ builder.TokenType, /* TContext: */ typeof(TContext), /* TKey: */ typeof(TKey))); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index ce761ca2..e38aa8b5 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -16,13 +16,11 @@ namespace OpenIddict { /// /// The type of the Token entity. /// The type of the Authorization entity. - /// The type of the User entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. - public class OpenIddictTokenStore : IOpenIddictTokenStore + public class OpenIddictTokenStore : IOpenIddictTokenStore where TToken : OpenIddictToken, new() where TAuthorization : OpenIddictAuthorization - where TUser : OpenIddictUser where TContext : DbContext where TKey : IEquatable { public OpenIddictTokenStore(TContext context) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs deleted file mode 100644 index 6ea9ee53..00000000 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictUserStore.cs +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace OpenIddict { - /// - /// Provides methods allowing to manage the users stored in a database. - /// - /// The type of the User entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Role entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictUserStore : - UserStore, IOpenIddictUserStore - where TUser : OpenIddictUser, new() - where TApplication : OpenIddictApplication - where TAuthorization : OpenIddictAuthorization - where TRole : IdentityRole - where TToken : OpenIddictToken, new() - where TContext : DbContext - where TKey : IEquatable { - public OpenIddictUserStore(TContext context) - : base(context) { } - - /// - /// Gets the database set corresponding to the entity. - /// - protected DbSet Applications => Context.Set(); - - /// - /// Creates a new token associated with the given user and defined by a unique identifier and a token type. - /// - /// The user associated with the token. - /// The token type. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - public virtual async Task CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty."); - } - - // Ensure that the key type can be serialized. - var converter = TypeDescriptor.GetConverter(typeof(TKey)); - if (!converter.CanConvertTo(typeof(string))) { - throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); - } - - var token = new TToken { Type = type }; - user.Tokens.Add(token); - - Context.Update(user); - - await Context.SaveChangesAsync(cancellationToken); - - return converter.ConvertToInvariantString(token.Id); - } - - /// - /// Creates a new token associated with the given user and - /// attached to the tokens issued to the specified client. - /// - /// The user. - /// The application. - /// The token type. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - public virtual async Task CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(client)) { - throw new ArgumentException("The client identifier cannot be null or empty."); - } - - if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The token type cannot be null or empty."); - } - - // Ensure that the key type can be serialized. - var converter = TypeDescriptor.GetConverter(typeof(TKey)); - if (!converter.CanConvertTo(typeof(string))) { - throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); - } - - var application = await Applications.FirstOrDefaultAsync(entity => entity.ClientId.Equals(client), cancellationToken); - if (application == null) { - throw new InvalidOperationException("The application cannot be found in the database."); - } - - var token = new TToken { Type = type }; - - application.Tokens.Add(token); - user.Tokens.Add(token); - - Context.Update(application); - Context.Update(user); - - await Context.SaveChangesAsync(cancellationToken); - - return converter.ConvertToInvariantString(token.Id); - } - - /// - /// Retrieves the token identifiers associated with a user. - /// - /// The user. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens associated with the user. - /// - public virtual async Task> GetTokensAsync(TUser user, CancellationToken cancellationToken) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - // Ensure that the key type can be serialized. - var converter = TypeDescriptor.GetConverter(typeof(TKey)); - if (!converter.CanConvertTo(typeof(string))) { - throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); - } - - var query = from entity in Users - where entity.Id.Equals(user.Id) - from token in entity.Tokens - select token.Id; - - var tokens = new List(); - - foreach (var identifier in await query.ToArrayAsync()) { - tokens.Add(converter.ConvertToInvariantString(identifier)); - } - - return tokens; - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/project.json b/src/OpenIddict.EntityFramework/project.json index ee2c09be..8548da3c 100644 --- a/src/OpenIddict.EntityFramework/project.json +++ b/src/OpenIddict.EntityFramework/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha2-*", + "version": "1.0.0-beta1-*", "description": "Entity Framework adapter for OpenIddict.", "authors": [ "Kévin Chalet" ], diff --git a/src/OpenIddict.Mvc/project.json b/src/OpenIddict.Mvc/project.json index cd5fd2c8..0ca37aef 100644 --- a/src/OpenIddict.Mvc/project.json +++ b/src/OpenIddict.Mvc/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha2-*", + "version": "1.0.0-beta1-*", "description": "OpenIddict binders for ASP.NET Core MVC.", "authors": [ "Kévin Chalet" ], diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 0055cc8d..17949eeb 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -6,7 +6,6 @@ using System; using JetBrains.Annotations; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OpenIddict; @@ -19,12 +18,6 @@ namespace Microsoft.AspNetCore.Builder { /// /// The type of the Entity Framework database context. /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// /// The . public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) where TContext : DbContext { @@ -32,102 +25,37 @@ namespace Microsoft.AspNetCore.Builder { throw new ArgumentNullException(nameof(services)); } - return services.AddOpenIddict(); + return services.AddOpenIddict(); } /// /// Registers the default OpenIddict services in the DI container, /// including the Entity Framework stores and the specified entities. /// - /// The type of the User entity. - /// The type of the Entity Framework database context. - /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TUser : OpenIddictUser - where TContext : DbContext { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - return services.AddOpenIddict(); - } - - /// - /// Registers the default OpenIddict services in the DI container, - /// including the Entity Framework stores and the specified entities. - /// - /// The type of the User entity. - /// The type of the Role entity. - /// The type of the Entity Framework database context. - /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TUser : OpenIddictUser - where TRole : IdentityRole - where TContext : DbContext { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - return services.AddOpenIddict(); - } - - /// - /// Registers the default OpenIddict services in the DI container, - /// including the Entity Framework stores and the specified entities. - /// - /// The type of the User entity. - /// The type of the Role entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TUser : OpenIddictUser - where TRole : IdentityRole + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) where TContext : DbContext where TKey : IEquatable { if (services == null) { throw new ArgumentNullException(nameof(services)); } - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TContext, TKey>(); + return services.AddOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken, TContext, TKey>(); } /// /// Registers the default OpenIddict services in the DI container, /// including the Entity Framework stores and the specified entities. /// - /// The type of the User entity. - /// The type of the Role entity. /// The type of the Application entity. /// The type of the Authorization entity. /// The type of the Scope entity. @@ -135,17 +63,9 @@ namespace Microsoft.AspNetCore.Builder { /// The type of the Entity Framework database context. /// The type of the entity primary keys. /// The services collection. - /// - /// Note: the core services include native support for the non-interactive flows - /// (resource owner password credentials, client credentials, refresh token). - /// To support interactive flows like authorization code or implicit/hybrid, - /// consider adding the MVC module or creating your own authorization controller. - /// /// The . - public static OpenIddictBuilder AddOpenIddict( + public static OpenIddictBuilder AddOpenIddict( [NotNull] this IServiceCollection services) - where TUser : OpenIddictUser - where TRole : IdentityRole where TApplication : OpenIddictApplication where TAuthorization : OpenIddictAuthorization where TScope : OpenIddictScope @@ -157,7 +77,7 @@ namespace Microsoft.AspNetCore.Builder { } // Register the OpenIddict core services and the default EntityFramework stores. - return services.AddOpenIddict() + return services.AddOpenIddict() .AddEntityFramework(); } } diff --git a/src/OpenIddict/project.json b/src/OpenIddict/project.json index c8e4da03..607192cd 100644 --- a/src/OpenIddict/project.json +++ b/src/OpenIddict/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha2-*", + "version": "1.0.0-beta1-*", "description": "Easy-to-use OpenID Connect server for ASP.NET Core.", "authors": [ "Kévin Chalet" ],