|
|
|
@ -3,12 +3,13 @@ using System.Collections.Generic; |
|
|
|
using System.ComponentModel.DataAnnotations; |
|
|
|
using System.Linq; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using IdentityServer4.Extensions; |
|
|
|
using IdentityServer4.Models; |
|
|
|
using IdentityServer4.Services; |
|
|
|
using IdentityServer4.Stores; |
|
|
|
using IdentityServer4.Validation; |
|
|
|
using Microsoft.AspNetCore.Mvc; |
|
|
|
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|
|
|
using Volo.Abp.UI; |
|
|
|
|
|
|
|
namespace Volo.Abp.Account.Web.Pages |
|
|
|
{ |
|
|
|
@ -24,9 +25,7 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
public string ReturnUrlHash { get; set; } |
|
|
|
|
|
|
|
[BindProperty] |
|
|
|
public ConsentModel.ConsentInputModel ConsentInput { get; set; } |
|
|
|
|
|
|
|
public ClientInfoModel ClientInfo { get; set; } |
|
|
|
public ConsentViewModel Consent { get; set; } |
|
|
|
|
|
|
|
private readonly IIdentityServerInteractionService _interaction; |
|
|
|
private readonly IClientStore _clientStore; |
|
|
|
@ -44,37 +43,7 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
|
|
|
|
public virtual async Task<IActionResult> OnGet() |
|
|
|
{ |
|
|
|
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
|
|
|
if (request == null) |
|
|
|
{ |
|
|
|
throw new ApplicationException($"No consent request matching request: {ReturnUrl}"); |
|
|
|
} |
|
|
|
|
|
|
|
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); |
|
|
|
if (client == null) |
|
|
|
{ |
|
|
|
throw new ApplicationException($"Invalid client id: {request.ClientId}"); |
|
|
|
} |
|
|
|
|
|
|
|
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); |
|
|
|
if (resources == null || (!resources.IdentityResources.Any() && !resources.ApiResources.Any())) |
|
|
|
{ |
|
|
|
throw new ApplicationException($"No scopes matching: {request.ScopesRequested.Aggregate((x, y) => x + ", " + y)}"); |
|
|
|
} |
|
|
|
|
|
|
|
ClientInfo = new ClientInfoModel(client); |
|
|
|
ConsentInput = new ConsentInputModel |
|
|
|
{ |
|
|
|
RememberConsent = true, |
|
|
|
IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, true)).ToList(), |
|
|
|
ApiScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, true)).ToList() |
|
|
|
}; |
|
|
|
|
|
|
|
if (resources.OfflineAccess) |
|
|
|
{ |
|
|
|
ConsentInput.ApiScopes.Add(GetOfflineAccessScope(true)); |
|
|
|
} |
|
|
|
|
|
|
|
Consent = await BuildViewModelAsync(ReturnUrl); |
|
|
|
return Page(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -96,53 +65,137 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
throw new ApplicationException("Unknown Error!"); |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual async Task<ConsentModel.ProcessConsentResult> ProcessConsentAsync() |
|
|
|
protected virtual async Task<ProcessConsentResult> ProcessConsentAsync() |
|
|
|
{ |
|
|
|
var result = new ConsentModel.ProcessConsentResult(); |
|
|
|
var result = new ProcessConsentResult(); |
|
|
|
|
|
|
|
// validate return url is still valid
|
|
|
|
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
|
|
|
if (request == null) |
|
|
|
{ |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
ConsentResponse grantedConsent; |
|
|
|
ConsentResponse grantedConsent = null; |
|
|
|
|
|
|
|
if (ConsentInput.UserDecision == "no") |
|
|
|
// user clicked 'no' - send back the standard 'access_denied' response
|
|
|
|
if (Consent?.Button == "no") |
|
|
|
{ |
|
|
|
grantedConsent = ConsentResponse.Denied; |
|
|
|
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; |
|
|
|
// emit event
|
|
|
|
//await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
|
|
|
|
} |
|
|
|
else |
|
|
|
// user clicked 'yes' - validate the data
|
|
|
|
else if (Consent?.Button == "yes") |
|
|
|
{ |
|
|
|
if (!ConsentInput.IdentityScopes.IsNullOrEmpty() || !ConsentInput.ApiScopes.IsNullOrEmpty()) |
|
|
|
// if the user consented to some scope, build the response model
|
|
|
|
if (!Consent.ScopesConsented.IsNullOrEmpty()) |
|
|
|
{ |
|
|
|
var scopes = Consent.ScopesConsented; |
|
|
|
if (ConsentOptions.EnableOfflineAccess == false) |
|
|
|
{ |
|
|
|
scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); |
|
|
|
} |
|
|
|
|
|
|
|
grantedConsent = new ConsentResponse |
|
|
|
{ |
|
|
|
RememberConsent = ConsentInput.RememberConsent, |
|
|
|
ScopesConsented = ConsentInput.GetAllowedScopeNames() |
|
|
|
RememberConsent = Consent.RememberConsent, |
|
|
|
ScopesValuesConsented = scopes.ToArray(), |
|
|
|
Description = Consent.Description |
|
|
|
}; |
|
|
|
|
|
|
|
// emit event
|
|
|
|
//await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
|
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
throw new UserFriendlyException("You must pick at least one permission"); //TODO: How to handle this
|
|
|
|
//throw new UserFriendlyException("You must pick at least one permission"); //TODO: How to handle this
|
|
|
|
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; |
|
|
|
} |
|
|
|
|
|
|
|
if (grantedConsent != null) |
|
|
|
{ |
|
|
|
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
|
|
|
if (request == null) |
|
|
|
{ |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
// communicate outcome of consent back to identityserver
|
|
|
|
await _interaction.GrantConsentAsync(request, grantedConsent); |
|
|
|
|
|
|
|
result.RedirectUri = ReturnUrl; //TODO: ReturnUrlHash?
|
|
|
|
// indicate that's it ok to redirect back to authorization endpoint
|
|
|
|
result.RedirectUri = Consent.ReturnUrl; //TODO: ReturnUrlHash?
|
|
|
|
result.Client = request.Client; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// we need to redisplay the consent UI
|
|
|
|
|
|
|
|
result.ViewModel = await BuildViewModelAsync(ReturnUrl, Consent); |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) |
|
|
|
private async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) |
|
|
|
{ |
|
|
|
var request = await _interaction.GetAuthorizationContextAsync(returnUrl); |
|
|
|
if (request != null) |
|
|
|
{ |
|
|
|
return CreateConsentViewModel(model, returnUrl, request); |
|
|
|
} |
|
|
|
|
|
|
|
throw new ApplicationException($"No consent request matching request: {returnUrl}"); |
|
|
|
} |
|
|
|
|
|
|
|
private ConsentViewModel CreateConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request) |
|
|
|
{ |
|
|
|
var consentViewModel = new ConsentViewModel |
|
|
|
{ |
|
|
|
RememberConsent = model?.RememberConsent ?? true, |
|
|
|
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(), |
|
|
|
Description = model?.Description, |
|
|
|
|
|
|
|
ReturnUrl = returnUrl, |
|
|
|
|
|
|
|
ClientName = request.Client.ClientName ?? request.Client.ClientId, |
|
|
|
ClientUrl = request.Client.ClientUri, |
|
|
|
ClientLogoUrl = request.Client.LogoUri, |
|
|
|
AllowRememberConsent = request.Client.AllowRememberConsent |
|
|
|
}; |
|
|
|
|
|
|
|
consentViewModel.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => |
|
|
|
CreateScopeViewModel(x, consentViewModel.ScopesConsented.Contains(x.Name) || model == null)) |
|
|
|
.ToArray(); |
|
|
|
|
|
|
|
var apiScopes = new List<ScopeViewModel>(); |
|
|
|
foreach(var parsedScope in request.ValidatedResources.ParsedScopes) |
|
|
|
{ |
|
|
|
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); |
|
|
|
if (apiScope != null) |
|
|
|
{ |
|
|
|
var scopeVm = CreateScopeViewModel(parsedScope, apiScope, |
|
|
|
consentViewModel.ScopesConsented.Contains(parsedScope.RawValue) || model == null); |
|
|
|
apiScopes.Add(scopeVm); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) |
|
|
|
{ |
|
|
|
apiScopes.Add(GetOfflineAccessScope(consentViewModel.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); |
|
|
|
} |
|
|
|
|
|
|
|
consentViewModel.ApiScopes = apiScopes; |
|
|
|
|
|
|
|
return consentViewModel; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected virtual ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) |
|
|
|
{ |
|
|
|
return new ConsentModel.ScopeViewModel |
|
|
|
return new ScopeViewModel |
|
|
|
{ |
|
|
|
Name = identity.Name, |
|
|
|
Value = identity.Name, |
|
|
|
DisplayName = identity.DisplayName, |
|
|
|
Description = identity.Description, |
|
|
|
Emphasize = identity.Emphasize, |
|
|
|
@ -151,24 +204,30 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(Scope scope, bool check) |
|
|
|
protected virtual ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) |
|
|
|
{ |
|
|
|
return new ConsentModel.ScopeViewModel |
|
|
|
{ |
|
|
|
Name = scope.Name, |
|
|
|
DisplayName = scope.DisplayName, |
|
|
|
Description = scope.Description, |
|
|
|
Emphasize = scope.Emphasize, |
|
|
|
Required = scope.Required, |
|
|
|
Checked = check || scope.Required |
|
|
|
var displayName = apiScope.DisplayName ?? apiScope.Name; |
|
|
|
if (!string.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) |
|
|
|
{ |
|
|
|
displayName += ":" + parsedScopeValue.ParsedParameter; |
|
|
|
} |
|
|
|
|
|
|
|
return new ScopeViewModel |
|
|
|
{ |
|
|
|
Value = parsedScopeValue.RawValue, |
|
|
|
DisplayName = displayName, |
|
|
|
Description = apiScope.Description, |
|
|
|
Emphasize = apiScope.Emphasize, |
|
|
|
Required = apiScope.Required, |
|
|
|
Checked = check || apiScope.Required |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual ConsentModel.ScopeViewModel GetOfflineAccessScope(bool check) |
|
|
|
protected virtual ScopeViewModel GetOfflineAccessScope(bool check) |
|
|
|
{ |
|
|
|
return new ConsentModel.ScopeViewModel |
|
|
|
return new ScopeViewModel |
|
|
|
{ |
|
|
|
Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, |
|
|
|
Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, |
|
|
|
DisplayName = "Offline Access", //TODO: Localize
|
|
|
|
Description = "Access to your applications and resources, even when you are offline", |
|
|
|
Emphasize = true, |
|
|
|
@ -178,28 +237,37 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
|
|
|
|
public class ConsentInputModel |
|
|
|
{ |
|
|
|
public List<ConsentModel.ScopeViewModel> IdentityScopes { get; set; } |
|
|
|
|
|
|
|
public List<ConsentModel.ScopeViewModel> ApiScopes { get; set; } |
|
|
|
|
|
|
|
[Required] |
|
|
|
public string UserDecision { get; set; } |
|
|
|
public string Button { get; set; } |
|
|
|
|
|
|
|
public IEnumerable<string> ScopesConsented { get; set; } |
|
|
|
public bool RememberConsent { get; set; } |
|
|
|
|
|
|
|
public List<string> GetAllowedScopeNames() |
|
|
|
{ |
|
|
|
var identityScopes = IdentityScopes ?? new List<ConsentModel.ScopeViewModel>(); |
|
|
|
var apiScopes = ApiScopes ?? new List<ConsentModel.ScopeViewModel>(); |
|
|
|
return identityScopes.Union(apiScopes).Where(s => s.Checked).Select(s => s.Name).ToList(); |
|
|
|
} |
|
|
|
public string ReturnUrl { get; set; } |
|
|
|
|
|
|
|
public string Description { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public class ConsentViewModel : ConsentInputModel |
|
|
|
{ |
|
|
|
public string ClientName { get; set; } |
|
|
|
public string ClientUrl { get; set; } |
|
|
|
|
|
|
|
public string ClientLogoUrl { get; set; } |
|
|
|
|
|
|
|
public bool AllowRememberConsent { get; set; } |
|
|
|
|
|
|
|
public IEnumerable<ScopeViewModel> IdentityScopes { get; set; } |
|
|
|
|
|
|
|
public IEnumerable<ScopeViewModel> ApiScopes { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
public class ScopeViewModel |
|
|
|
{ |
|
|
|
[Required] |
|
|
|
[HiddenInput] |
|
|
|
public string Name { get; set; } |
|
|
|
public string Value { get; set; } |
|
|
|
|
|
|
|
public bool Checked { get; set; } |
|
|
|
|
|
|
|
@ -216,29 +284,13 @@ namespace Volo.Abp.Account.Web.Pages |
|
|
|
{ |
|
|
|
public bool IsRedirect => RedirectUri != null; |
|
|
|
public string RedirectUri { get; set; } |
|
|
|
public Client Client { get; set; } |
|
|
|
|
|
|
|
public bool ShowView => ViewModel != null; |
|
|
|
public ConsentViewModel ViewModel { get; set; } |
|
|
|
|
|
|
|
public bool HasValidationError => ValidationError != null; |
|
|
|
public string ValidationError { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
public class ClientInfoModel |
|
|
|
{ |
|
|
|
public string ClientName { get; set; } |
|
|
|
|
|
|
|
public string ClientUrl { get; set; } |
|
|
|
|
|
|
|
public string ClientLogoUrl { get; set; } |
|
|
|
|
|
|
|
public bool AllowRememberConsent { get; set; } |
|
|
|
|
|
|
|
public ClientInfoModel(Client client) |
|
|
|
{ |
|
|
|
//TODO: Automap
|
|
|
|
ClientName = client.ClientId; |
|
|
|
ClientUrl = client.ClientUri; |
|
|
|
ClientLogoUrl = client.LogoUri; |
|
|
|
AllowRememberConsent = client.AllowRememberConsent; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|