|
|
|
@ -6,6 +6,8 @@ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Collections.Immutable; |
|
|
|
using System.Linq; |
|
|
|
using System.Security.Claims; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using Microsoft.AspNetCore; |
|
|
|
@ -14,6 +16,7 @@ using Microsoft.AspNetCore.Authorization; |
|
|
|
using Microsoft.AspNetCore.Http; |
|
|
|
using Microsoft.AspNetCore.Identity; |
|
|
|
using Microsoft.AspNetCore.Mvc; |
|
|
|
using Microsoft.Extensions.Primitives; |
|
|
|
using Mvc.Server.Helpers; |
|
|
|
using Mvc.Server.Models; |
|
|
|
using Mvc.Server.ViewModels.Authorization; |
|
|
|
@ -28,15 +31,18 @@ namespace Mvc.Server |
|
|
|
public class AuthorizationController : Controller |
|
|
|
{ |
|
|
|
private readonly OpenIddictApplicationManager<OpenIddictApplication> _applicationManager; |
|
|
|
private readonly OpenIddictAuthorizationManager<OpenIddictAuthorization> _authorizationManager; |
|
|
|
private readonly SignInManager<ApplicationUser> _signInManager; |
|
|
|
private readonly UserManager<ApplicationUser> _userManager; |
|
|
|
|
|
|
|
public AuthorizationController( |
|
|
|
OpenIddictApplicationManager<OpenIddictApplication> applicationManager, |
|
|
|
OpenIddictAuthorizationManager<OpenIddictAuthorization> authorizationManager, |
|
|
|
SignInManager<ApplicationUser> signInManager, |
|
|
|
UserManager<ApplicationUser> userManager) |
|
|
|
{ |
|
|
|
_applicationManager = applicationManager; |
|
|
|
_authorizationManager = authorizationManager; |
|
|
|
_signInManager = signInManager; |
|
|
|
_userManager = userManager; |
|
|
|
} |
|
|
|
@ -45,21 +51,160 @@ namespace Mvc.Server |
|
|
|
// Note: to support interactive flows like the code flow,
|
|
|
|
// you must provide your own authorization endpoint action:
|
|
|
|
|
|
|
|
[Authorize, HttpGet("~/connect/authorize")] |
|
|
|
[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."); |
|
|
|
|
|
|
|
// Retrieve the user principal stored in the authentication cookie.
|
|
|
|
// If it can't be extracted, redirect the user to the login page.
|
|
|
|
var result = await HttpContext.AuthenticateAsync(); |
|
|
|
if (result == null || !result.Succeeded) |
|
|
|
{ |
|
|
|
// If the client application requested promptless authentication,
|
|
|
|
// return an error indicating that the user is not logged in.
|
|
|
|
if (request.HasPrompt(Prompts.None)) |
|
|
|
{ |
|
|
|
return Forbid(new AuthenticationProperties(new Dictionary<string, string> |
|
|
|
{ |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." |
|
|
|
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
} |
|
|
|
|
|
|
|
return Challenge(new AuthenticationProperties |
|
|
|
{ |
|
|
|
RedirectUri = Request.PathBase + Request.Path + QueryString.Create( |
|
|
|
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// If prompt=login was specified by the client application,
|
|
|
|
// immediately return the user agent to the login page.
|
|
|
|
if (request.HasPrompt(Prompts.Login)) |
|
|
|
{ |
|
|
|
// To avoid endless login -> authorization redirects, the prompt=login flag
|
|
|
|
// is removed from the authorization request payload before redirecting the user.
|
|
|
|
var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login)); |
|
|
|
|
|
|
|
var parameters = Request.HasFormContentType ? |
|
|
|
Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : |
|
|
|
Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList(); |
|
|
|
|
|
|
|
parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); |
|
|
|
|
|
|
|
return Challenge(new AuthenticationProperties |
|
|
|
{ |
|
|
|
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// If a max_age parameter was provided, ensure that the cookie is not too old.
|
|
|
|
// If it's too old, automatically redirect the user agent to the login page.
|
|
|
|
if (request.MaxAge != null && result.Properties.IssuedUtc != null && |
|
|
|
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)) |
|
|
|
{ |
|
|
|
if (request.HasPrompt(Prompts.None)) |
|
|
|
{ |
|
|
|
return Forbid(new AuthenticationProperties(new Dictionary<string, string> |
|
|
|
{ |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." |
|
|
|
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
} |
|
|
|
|
|
|
|
return Challenge(new AuthenticationProperties |
|
|
|
{ |
|
|
|
RedirectUri = Request.PathBase + Request.Path + QueryString.Create( |
|
|
|
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Retrieve the profile of the logged in user.
|
|
|
|
var user = await _userManager.GetUserAsync(User) ?? |
|
|
|
throw new InvalidOperationException("The user details cannot be retrieved."); |
|
|
|
|
|
|
|
// Retrieve the application details from the database.
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? |
|
|
|
throw new InvalidOperationException("Details concerning the calling client application cannot be found."); |
|
|
|
|
|
|
|
return View(new AuthorizeViewModel |
|
|
|
// Retrieve the permanent authorizations associated with the user and the calling client application.
|
|
|
|
var authorizations = await _authorizationManager.FindAsync( |
|
|
|
subject: User.FindFirst(Claims.Subject)?.Value, |
|
|
|
client : await _applicationManager.GetIdAsync(application), |
|
|
|
status : Statuses.Valid, |
|
|
|
type : AuthorizationTypes.Permanent, |
|
|
|
scopes : ImmutableArray.CreateRange(request.GetScopes())).ToListAsync(); |
|
|
|
|
|
|
|
switch (await _applicationManager.GetConsentTypeAsync(application)) |
|
|
|
{ |
|
|
|
ApplicationName = await _applicationManager.GetDisplayNameAsync(application), |
|
|
|
Scope = request.Scope |
|
|
|
}); |
|
|
|
// If the consent is external (e.g when authorizations are granted by a sysadmin),
|
|
|
|
// immediately return an error if no authorization can be found in the database.
|
|
|
|
case ConsentTypes.External when !authorizations.Any(): |
|
|
|
return Forbid(new AuthenticationProperties(new Dictionary<string, string> |
|
|
|
{ |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = |
|
|
|
"The logged in user is not allowed to access this client application." |
|
|
|
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
|
|
|
|
// If the consent is implicit or if an authorization was found,
|
|
|
|
// return an authorization response without displaying the consent form.
|
|
|
|
case ConsentTypes.Implicit: |
|
|
|
case ConsentTypes.External when authorizations.Any(): |
|
|
|
case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): |
|
|
|
var principal = await _signInManager.CreateUserPrincipalAsync(user); |
|
|
|
|
|
|
|
// Note: in this sample, the granted scopes match the requested scope
|
|
|
|
// but you may want to allow the user to uncheck specific scopes.
|
|
|
|
// For that, simply restrict the list of scopes before calling SetScopes.
|
|
|
|
principal.SetScopes(request.GetScopes()); |
|
|
|
principal.SetResources("resource_server"); |
|
|
|
|
|
|
|
// Automatically create a permanent authorization to avoid requiring explicit consent
|
|
|
|
// for future authorization or token requests containing the same scopes.
|
|
|
|
var authorization = authorizations.LastOrDefault(); |
|
|
|
if (authorization == null) |
|
|
|
{ |
|
|
|
authorization = await _authorizationManager.CreateAsync( |
|
|
|
principal: principal, |
|
|
|
subject : principal.FindFirst(Claims.Subject)?.Value, |
|
|
|
client : await _applicationManager.GetIdAsync(application), |
|
|
|
type : AuthorizationTypes.Permanent, |
|
|
|
scopes : ImmutableArray.CreateRange(principal.GetScopes())); |
|
|
|
} |
|
|
|
|
|
|
|
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); |
|
|
|
|
|
|
|
foreach (var claim in principal.Claims) |
|
|
|
{ |
|
|
|
claim.SetDestinations(GetDestinations(claim, principal)); |
|
|
|
} |
|
|
|
|
|
|
|
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
|
|
|
|
// At this point, no authorization was found in the database and an error must be returned
|
|
|
|
// if the client application specified prompt=none in the authorization request.
|
|
|
|
case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): |
|
|
|
case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): |
|
|
|
return Forbid(new AuthenticationProperties(new Dictionary<string, string> |
|
|
|
{ |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = |
|
|
|
"Interactive user consent is required.", |
|
|
|
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
|
|
|
|
// In every other case, render the consent form.
|
|
|
|
default: |
|
|
|
return View(new AuthorizeViewModel |
|
|
|
{ |
|
|
|
ApplicationName = await _applicationManager.GetDisplayNameAsync(application), |
|
|
|
Scope = request.Scope |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Authorize, FormValueRequired("submit.Accept")] |
|
|
|
@ -73,6 +218,32 @@ namespace Mvc.Server |
|
|
|
var user = await _userManager.GetUserAsync(User) ?? |
|
|
|
throw new InvalidOperationException("The user details cannot be retrieved."); |
|
|
|
|
|
|
|
// Retrieve the application details from the database.
|
|
|
|
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? |
|
|
|
throw new InvalidOperationException("Details concerning the calling client application cannot be found."); |
|
|
|
|
|
|
|
// Retrieve the permanent authorizations associated with the user and the calling client application.
|
|
|
|
var authorizations = await _authorizationManager.FindAsync( |
|
|
|
subject: User.FindFirst(Claims.Subject)?.Value, |
|
|
|
client : await _applicationManager.GetIdAsync(application), |
|
|
|
status : Statuses.Valid, |
|
|
|
type : AuthorizationTypes.Permanent, |
|
|
|
scopes : ImmutableArray.CreateRange(request.GetScopes())).ToListAsync(); |
|
|
|
|
|
|
|
// Note: the same check is already made in the other action but is repeated
|
|
|
|
// here to ensure a malicious user can't abuse this POST-only endpoint and
|
|
|
|
// force it to return a valid response without the external authorization.
|
|
|
|
switch (await _applicationManager.GetConsentTypeAsync(application)) |
|
|
|
{ |
|
|
|
case ConsentTypes.External when !authorizations.Any(): |
|
|
|
return Forbid(new AuthenticationProperties(new Dictionary<string, string> |
|
|
|
{ |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, |
|
|
|
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = |
|
|
|
"The logged in user is not allowed to access this client application." |
|
|
|
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|
|
|
} |
|
|
|
|
|
|
|
var principal = await _signInManager.CreateUserPrincipalAsync(user); |
|
|
|
|
|
|
|
// Note: in this sample, the granted scopes match the requested scope
|
|
|
|
@ -81,6 +252,21 @@ namespace Mvc.Server |
|
|
|
principal.SetScopes(request.GetScopes()); |
|
|
|
principal.SetResources("resource_server"); |
|
|
|
|
|
|
|
// Automatically create a permanent authorization to avoid requiring explicit consent
|
|
|
|
// for future authorization or token requests containing the same scopes.
|
|
|
|
var authorization = authorizations.LastOrDefault(); |
|
|
|
if (authorization == null) |
|
|
|
{ |
|
|
|
authorization = await _authorizationManager.CreateAsync( |
|
|
|
principal: principal, |
|
|
|
subject : principal.FindFirst(Claims.Subject)?.Value, |
|
|
|
client : await _applicationManager.GetIdAsync(application), |
|
|
|
type : AuthorizationTypes.Permanent, |
|
|
|
scopes : ImmutableArray.CreateRange(principal.GetScopes())); |
|
|
|
} |
|
|
|
|
|
|
|
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); |
|
|
|
|
|
|
|
foreach (var claim in principal.Claims) |
|
|
|
{ |
|
|
|
claim.SetDestinations(GetDestinations(claim, principal)); |
|
|
|
|