7 changed files with 115 additions and 319 deletions
@ -1,180 +0,0 @@ |
|||||
using System.Diagnostics; |
|
||||
using System.Linq; |
|
||||
using System.Security.Claims; |
|
||||
using System.Threading.Tasks; |
|
||||
using AspNet.Security.OpenIdConnect.Extensions; |
|
||||
using JetBrains.Annotations; |
|
||||
using Microsoft.AspNetCore.Authentication; |
|
||||
using Microsoft.AspNetCore.Builder; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
using Microsoft.AspNetCore.Http.Authentication; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
|
|
||||
namespace OpenIddict.Infrastructure { |
|
||||
public class OpenIddictMiddleware<TUser, TApplication, TAuthorization, TScope, TToken> |
|
||||
where TUser : class where TApplication : class |
|
||||
where TAuthorization : class where TScope : class where TToken : class { |
|
||||
private readonly RequestDelegate next; |
|
||||
|
|
||||
public OpenIddictMiddleware([NotNull] RequestDelegate next) { |
|
||||
this.next = next; |
|
||||
} |
|
||||
|
|
||||
public async Task Invoke([NotNull] HttpContext context) { |
|
||||
// Invoke the rest of the pipeline to allow handling
|
|
||||
// authorization, logout or token requests in user code.
|
|
||||
await next(context); |
|
||||
|
|
||||
// If the request was already handled, skip the default logic.
|
|
||||
if (context.Response.HasStarted || context.Response.StatusCode != 404) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// If the request doesn't correspond to an OpenID Connect request, ignore it.
|
|
||||
var request = context.GetOpenIdConnectRequest(); |
|
||||
if (request == null || (!request.IsAuthorizationRequest() && |
|
||||
!request.IsLogoutRequest() && |
|
||||
!request.IsTokenRequest())) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// If an OpenID Connect response was already prepared, bypass the default logic.
|
|
||||
var response = context.GetOpenIdConnectResponse(); |
|
||||
if (response != null) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// Resolve the OpenIddict services from the scoped container.
|
|
||||
var services = context.RequestServices.GetRequiredService<OpenIddictServices< |
|
||||
TUser, TApplication, TAuthorization, TScope, TToken>>(); |
|
||||
|
|
||||
// Reset the response status code to allow the OpenID Connect server
|
|
||||
// middleware to apply a challenge, signin or logout response.
|
|
||||
context.Response.StatusCode = 200; |
|
||||
|
|
||||
ClaimsPrincipal principal = null; |
|
||||
|
|
||||
if (request.IsAuthorizationRequest()) { |
|
||||
// If the user is not logged in, return a challenge response.
|
|
||||
if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) { |
|
||||
await context.Authentication.ChallengeAsync(); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// Retrieve the profile of the logged in user. If the user
|
|
||||
// cannot be found, return a challenge response.
|
|
||||
var user = await services.Users.GetUserAsync(context.User); |
|
||||
if (user == null) { |
|
||||
await context.Authentication.ChallengeAsync(); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
services.Logger.LogInformation("The authorization request was handled without asking for user consent."); |
|
||||
|
|
||||
principal = new ClaimsPrincipal(await services.Users.CreateIdentityAsync(user, request.GetScopes())); |
|
||||
} |
|
||||
|
|
||||
else if (request.IsLogoutRequest()) { |
|
||||
// Ask ASP.NET Core Identity to delete the local and external cookies created
|
|
||||
// when the user agent is redirected from the external identity provider
|
|
||||
// after a successful authentication flow (e.g Google or Facebook).
|
|
||||
await services.SignIn.SignOutAsync(); |
|
||||
|
|
||||
await context.Authentication.SignOutAsync(services.Options.AuthenticationScheme); |
|
||||
|
|
||||
services.Logger.LogInformation("The logout request was handled without asking for user consent."); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
else if (request.IsTokenRequest()) { |
|
||||
Debug.Assert(request.IsClientCredentialsGrantType() || request.IsPasswordGrantType(), |
|
||||
"Only grant_type=client_credentials and grant_type=password requests should be handled here."); |
|
||||
|
|
||||
services.Logger.LogInformation("The token request was automatically handled."); |
|
||||
|
|
||||
if (request.IsClientCredentialsGrantType()) { |
|
||||
// Retrieve the application details corresponding to the requested client_id.
|
|
||||
// Note: this call shouldn't return a null instance, but a race condition may occur
|
|
||||
// if the application was removed after the initial check made by ValidateTokenRequest.
|
|
||||
var application = await services.Applications.FindByClientIdAsync(request.ClientId); |
|
||||
if (application == null) { |
|
||||
services.Logger.LogError("The token request was aborted because the client application " + |
|
||||
"was not found in the database: '{ClientId}'.", request.ClientId); |
|
||||
|
|
||||
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var identity = new ClaimsIdentity(services.Options.AuthenticationScheme); |
|
||||
|
|
||||
// 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, request.ClientId); |
|
||||
|
|
||||
identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application), |
|
||||
OpenIdConnectConstants.Destinations.AccessToken, |
|
||||
OpenIdConnectConstants.Destinations.IdentityToken); |
|
||||
|
|
||||
principal = new ClaimsPrincipal(identity); |
|
||||
} |
|
||||
|
|
||||
else if (request.IsPasswordGrantType()) { |
|
||||
// Retrieve the user profile corresponding to the specified username.
|
|
||||
var user = await services.Users.FindByNameAsync(request.Username); |
|
||||
if (user == null) { |
|
||||
services.Logger.LogError("The token request was rejected because no user profile corresponding to " + |
|
||||
"the specified username was found: '{Username}'.", request.Username); |
|
||||
|
|
||||
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// Ensure the username/password couple is valid.
|
|
||||
if (!await services.Users.CheckPasswordAsync(user, request.Password)) { |
|
||||
services.Logger.LogError("The token request was rejected because the password didn't match " + |
|
||||
"the password associated with the account '{Username}'.", request.Username); |
|
||||
|
|
||||
if (services.Users.SupportsUserLockout) { |
|
||||
await services.Users.AccessFailedAsync(user); |
|
||||
|
|
||||
if (await services.Users.IsLockedOutAsync(user)) { |
|
||||
services.Logger.LogError("The token request was rejected because the account '{Username}' " + |
|
||||
"was locked out to prevent brute force attacks.", request.Username); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (services.Users.SupportsUserLockout) { |
|
||||
await services.Users.ResetAccessFailedCountAsync(user); |
|
||||
} |
|
||||
|
|
||||
principal = new ClaimsPrincipal(await services.Users.CreateIdentityAsync(user, request.GetScopes())); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// At this stage, don't alter the response
|
|
||||
// if a sign-in operation can't be performed.
|
|
||||
if (principal != null) { |
|
||||
// Create a new authentication ticket holding the user identity.
|
|
||||
var ticket = new AuthenticationTicket( |
|
||||
principal, new AuthenticationProperties(), |
|
||||
services.Options.AuthenticationScheme); |
|
||||
|
|
||||
ticket.SetResources(request.GetResources()); |
|
||||
ticket.SetScopes(request.GetScopes()); |
|
||||
|
|
||||
await context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,44 +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 AspNet.Security.OpenIdConnect.Server; |
|
||||
|
|
||||
namespace OpenIddict { |
|
||||
/// <summary>
|
|
||||
/// Exposes the default values used by OpenIddict.
|
|
||||
/// </summary>
|
|
||||
public static class OpenIddictDefaults { |
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.AuthorizationEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string AuthorizationEndpointPath = "/connect/authorize"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.IntrospectionEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string IntrospectionEndpointPath = "/connect/introspect"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.LogoutEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string LogoutEndpointPath = "/connect/logout"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.RevocationEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string RevocationEndpointPath = "/connect/revoke"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.TokenEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string TokenEndpointPath = "/connect/token"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Default value for <see cref="OpenIdConnectServerOptions.UserinfoEndpointPath"/>.
|
|
||||
/// </summary>
|
|
||||
public const string UserinfoEndpointPath = "/connect/userinfo"; |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue