You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
11 KiB
244 lines
11 KiB
using System.Security.Claims;
|
|
using Microsoft.AspNetCore;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OpenIddict.Abstractions;
|
|
using OpenIddict.Sandbox.AspNetCore.CimdServer.Models;
|
|
using OpenIddict.Server.AspNetCore;
|
|
using static OpenIddict.Abstractions.OpenIddictConstants;
|
|
|
|
namespace OpenIddict.Sandbox.AspNetCore.CimdServer;
|
|
|
|
public class AuthorizationController : Controller
|
|
{
|
|
private readonly IOpenIddictApplicationManager _applicationManager;
|
|
private readonly IOpenIddictAuthorizationManager _authorizationManager;
|
|
private readonly IOpenIddictScopeManager _scopeManager;
|
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
|
|
public AuthorizationController(
|
|
IOpenIddictApplicationManager applicationManager,
|
|
IOpenIddictAuthorizationManager authorizationManager,
|
|
IOpenIddictScopeManager scopeManager,
|
|
SignInManager<ApplicationUser> signInManager,
|
|
UserManager<ApplicationUser> userManager)
|
|
{
|
|
_applicationManager = applicationManager;
|
|
_authorizationManager = authorizationManager;
|
|
_scopeManager = scopeManager;
|
|
_signInManager = signInManager;
|
|
_userManager = userManager;
|
|
}
|
|
|
|
[HttpGet("~/connect/authorize")]
|
|
[HttpPost("~/connect/authorize")]
|
|
[IgnoreAntiforgeryToken]
|
|
public async Task<IActionResult> Authorize()
|
|
{
|
|
var request = HttpContext.GetOpenIddictServerRequest() ??
|
|
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
|
|
|
|
// Try to retrieve the user principal stored in the authentication cookie.
|
|
// If the cookie is stale (e.g. database was recreated), sign out and re-authenticate.
|
|
var result = await HttpContext.AuthenticateAsync();
|
|
if (result is { Succeeded: true })
|
|
{
|
|
var existing = await _userManager.GetUserAsync(result.Principal!);
|
|
if (existing is null)
|
|
{
|
|
// The cookie references a user that no longer exists. Sign out and retry.
|
|
await _signInManager.SignOutAsync();
|
|
result = AuthenticateResult.NoResult();
|
|
}
|
|
}
|
|
|
|
if (result is not { Succeeded: true })
|
|
{
|
|
// For this demo, auto-sign in the test user to simplify testing.
|
|
var user = await _userManager.FindByNameAsync("testuser");
|
|
if (user is not null)
|
|
{
|
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
|
|
|
// Redirect back to the same URL so the cookie is sent on the next request.
|
|
return Redirect(HttpContext.Request.PathBase + HttpContext.Request.Path + HttpContext.Request.QueryString);
|
|
}
|
|
|
|
return Forbid(
|
|
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|
properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|
{
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
|
|
}));
|
|
}
|
|
|
|
var userEntity = await _userManager.GetUserAsync(result.Principal!) ??
|
|
throw new InvalidOperationException("The user details cannot be retrieved.");
|
|
|
|
// Auto-approve consent for this demonstrator.
|
|
var identity = new ClaimsIdentity(
|
|
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
|
|
nameType: Claims.Name,
|
|
roleType: Claims.Role);
|
|
|
|
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(userEntity))
|
|
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(userEntity))
|
|
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(userEntity));
|
|
|
|
identity.SetScopes(request.GetScopes());
|
|
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
|
|
|
|
// Look up the application (for CIMD clients, this returns a virtual application
|
|
// synthesized from the metadata document by the CIMD application manager).
|
|
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!);
|
|
var applicationId = application is not null ? await _applicationManager.GetIdAsync(application) : null;
|
|
|
|
// Only create an authorization entry if the application has a database identity
|
|
// (CIMD virtual applications return null for GetIdAsync).
|
|
if (!string.IsNullOrEmpty(applicationId))
|
|
{
|
|
var authorization = await _authorizationManager.CreateAsync(
|
|
identity: identity,
|
|
subject: await _userManager.GetUserIdAsync(userEntity),
|
|
client: applicationId,
|
|
type: AuthorizationTypes.Permanent,
|
|
scopes: identity.GetScopes());
|
|
|
|
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
|
|
}
|
|
|
|
identity.SetDestinations(GetDestinations);
|
|
|
|
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
|
}
|
|
|
|
[HttpPost("~/connect/token")]
|
|
[IgnoreAntiforgeryToken]
|
|
[Produces("application/json")]
|
|
public async Task<IActionResult> Exchange()
|
|
{
|
|
var request = HttpContext.GetOpenIddictServerRequest() ??
|
|
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
|
|
|
|
if (request.IsPasswordGrantType())
|
|
{
|
|
var user = await _userManager.FindByNameAsync(request.Username!);
|
|
if (user is null)
|
|
{
|
|
return Forbid(
|
|
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|
properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|
{
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
|
|
}));
|
|
}
|
|
|
|
var passwordResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password!, lockoutOnFailure: false);
|
|
if (!passwordResult.Succeeded)
|
|
{
|
|
return Forbid(
|
|
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|
properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|
{
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
|
|
}));
|
|
}
|
|
|
|
var identity = new ClaimsIdentity(
|
|
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
|
|
nameType: Claims.Name,
|
|
roleType: Claims.Role);
|
|
|
|
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
|
|
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
|
|
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user));
|
|
|
|
identity.SetScopes(request.GetScopes());
|
|
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
|
|
identity.SetDestinations(GetDestinations);
|
|
|
|
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
|
}
|
|
|
|
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
|
|
{
|
|
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
|
|
|
var user = await _userManager.FindByIdAsync(result.Principal!.GetClaim(Claims.Subject)!);
|
|
if (user is null)
|
|
{
|
|
return Forbid(
|
|
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|
properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|
{
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
|
|
}));
|
|
}
|
|
|
|
if (!await _signInManager.CanSignInAsync(user))
|
|
{
|
|
return Forbid(
|
|
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|
properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|
{
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
|
|
}));
|
|
}
|
|
|
|
var identity = new ClaimsIdentity(result.Principal!.Claims,
|
|
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
|
|
nameType: Claims.Name,
|
|
roleType: Claims.Role);
|
|
|
|
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
|
|
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
|
|
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user));
|
|
|
|
// Preserve the scopes originally granted so refresh tokens continue to be issued.
|
|
identity.SetScopes(result.Principal!.GetScopes());
|
|
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
|
|
|
|
identity.SetDestinations(GetDestinations);
|
|
|
|
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
|
}
|
|
|
|
throw new InvalidOperationException("The specified grant type is not supported.");
|
|
}
|
|
|
|
private static IEnumerable<string> GetDestinations(Claim claim)
|
|
{
|
|
switch (claim.Type)
|
|
{
|
|
case Claims.Name:
|
|
yield return Destinations.AccessToken;
|
|
|
|
if (claim.Subject!.HasScope(Scopes.Profile))
|
|
yield return Destinations.IdentityToken;
|
|
|
|
yield break;
|
|
|
|
case Claims.Email:
|
|
yield return Destinations.AccessToken;
|
|
|
|
if (claim.Subject!.HasScope(Scopes.Email))
|
|
yield return Destinations.IdentityToken;
|
|
|
|
yield break;
|
|
|
|
case "AspNet.Identity.SecurityStamp": yield break;
|
|
|
|
default:
|
|
yield return Destinations.AccessToken;
|
|
yield break;
|
|
}
|
|
}
|
|
}
|
|
|