mirror of https://github.com/abpframework/abp.git
4 changed files with 364 additions and 2 deletions
@ -0,0 +1,113 @@ |
|||
@page |
|||
@using Volo.Abp.Account.Web.Pages |
|||
@using Volo.Abp.Account.Web.Pages.Account |
|||
@model ConsentModel |
|||
<abp-card id="IdentityServerConsentWrapper"> |
|||
<abp-card-header> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<h2> |
|||
@if (Model.ClientInfo.ClientLogoUrl != null) |
|||
{ |
|||
<img src="@Model.ClientInfo.ClientLogoUrl"> |
|||
} |
|||
|
|||
@Model.ClientInfo.ClientName |
|||
<small>is requesting your permission</small> |
|||
</h2> |
|||
</div> |
|||
</div> |
|||
</abp-card-header> |
|||
<abp-card-body> |
|||
<form method="post" asp-page="/Consent"> |
|||
<input type="hidden" asp-for="ReturnUrl" /> |
|||
<input type="hidden" asp-for="ReturnUrlHash" /> |
|||
|
|||
<div>Uncheck the permissions you do not wish to grant.</div> |
|||
|
|||
@if (Model.ConsentInput.IdentityScopes.Any()) |
|||
{ |
|||
<h3>Personal Information</h3> |
|||
|
|||
<ul class="list-group"> |
|||
@for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++) |
|||
{ |
|||
<li class="list-group-item"> |
|||
<div class="form-check"> |
|||
<label asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-label"> |
|||
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-input" /> |
|||
@Model.ConsentInput.IdentityScopes[i].DisplayName |
|||
@if (Model.ConsentInput.IdentityScopes[i].Required) |
|||
{ |
|||
<span><em>(required)</em></span> |
|||
} |
|||
</label> |
|||
</div> |
|||
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@ |
|||
@if (Model.ConsentInput.IdentityScopes[i].Description != null) |
|||
{ |
|||
<div class="consent-description"> |
|||
@Model.ConsentInput.IdentityScopes[i].Description |
|||
</div> |
|||
} |
|||
</li> |
|||
} |
|||
</ul> |
|||
} |
|||
|
|||
@if (Model.ConsentInput.ApiScopes.Any()) |
|||
{ |
|||
<h3>Application Access</h3> |
|||
|
|||
<ul class="list-group"> |
|||
@for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++) |
|||
{ |
|||
<li class="list-group-item"> |
|||
<div class="form-check"> |
|||
<label asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-label"> |
|||
<input asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-input" disabled="@Model.ConsentInput.ApiScopes[i].Required" /> |
|||
@Model.ConsentInput.ApiScopes[i].DisplayName |
|||
@if (Model.ConsentInput.ApiScopes[i].Required) |
|||
{ |
|||
<span><em>(required)</em></span> |
|||
} |
|||
</label> |
|||
</div> |
|||
<input asp-for="@Model.ConsentInput.ApiScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@ |
|||
@if (Model.ConsentInput.ApiScopes[i].Description != null) |
|||
{ |
|||
<div class="consent-description"> |
|||
@Model.ConsentInput.ApiScopes[i].Description |
|||
</div> |
|||
} |
|||
</li> |
|||
} |
|||
</ul> |
|||
} |
|||
|
|||
@if (Model.ClientInfo.AllowRememberConsent) |
|||
{ |
|||
<div class="form-check"> |
|||
<label asp-for="@Model.ConsentInput.RememberConsent" class="form-check-label"> |
|||
<input asp-for="@Model.ConsentInput.RememberConsent" class="form-check-input" /> |
|||
<strong>Remember My Decision</strong> |
|||
</label> |
|||
</div> |
|||
} |
|||
|
|||
<div> |
|||
<button name="UserDecision" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button> |
|||
<button name="UserDecision" value="no" class="btn">No, Do Not Allow</button> |
|||
@if (Model.ClientInfo.ClientUrl != null) |
|||
{ |
|||
<a class="pull-right btn btn-secondary" target="_blank" href="@Model.ClientInfo.ClientUrl"> |
|||
<strong>@Model.ClientInfo.ClientName</strong> |
|||
</a> |
|||
} |
|||
</div> |
|||
|
|||
<div asp-validation-summary="All" class="text-danger"></div> |
|||
|
|||
</form> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
@ -0,0 +1,240 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Services; |
|||
using IdentityServer4.Stores; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.UI; |
|||
|
|||
namespace Volo.Abp.Account.Web.Pages |
|||
{ |
|||
//TODO: Move this into the Account folder!!!
|
|||
public class ConsentModel : AbpPageModel |
|||
{ |
|||
[HiddenInput] |
|||
[BindProperty(SupportsGet = true)] |
|||
public string ReturnUrl { get; set; } |
|||
|
|||
[HiddenInput] |
|||
[BindProperty(SupportsGet = true)] |
|||
public string ReturnUrlHash { get; set; } |
|||
|
|||
[BindProperty] |
|||
public ConsentModel.ConsentInputModel ConsentInput { get; set; } |
|||
|
|||
public ClientInfoModel ClientInfo { get; set; } |
|||
|
|||
private readonly IIdentityServerInteractionService _interaction; |
|||
private readonly IClientStore _clientStore; |
|||
private readonly IResourceStore _resourceStore; |
|||
|
|||
public ConsentModel( |
|||
IIdentityServerInteractionService interaction, |
|||
IClientStore clientStore, |
|||
IResourceStore resourceStore) |
|||
{ |
|||
_interaction = interaction; |
|||
_clientStore = clientStore; |
|||
_resourceStore = resourceStore; |
|||
} |
|||
|
|||
public virtual async Task 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)); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task<IActionResult> OnPost(string userDecision) |
|||
{ |
|||
var result = await ProcessConsentAsync(); |
|||
|
|||
if (result.IsRedirect) |
|||
{ |
|||
return Redirect(result.RedirectUri); |
|||
} |
|||
|
|||
if (result.HasValidationError) |
|||
{ |
|||
//ModelState.AddModelError("", result.ValidationError);
|
|||
throw new ApplicationException("Error: " + result.ValidationError); |
|||
} |
|||
|
|||
throw new ApplicationException("Unknown Error!"); |
|||
} |
|||
|
|||
protected virtual async Task<ConsentModel.ProcessConsentResult> ProcessConsentAsync() |
|||
{ |
|||
var result = new ConsentModel.ProcessConsentResult(); |
|||
|
|||
ConsentResponse grantedConsent; |
|||
|
|||
if (ConsentInput.UserDecision == "no") |
|||
{ |
|||
grantedConsent = ConsentResponse.Denied; |
|||
} |
|||
else |
|||
{ |
|||
if (ConsentInput.IdentityScopes.Any() || ConsentInput.ApiScopes.Any()) |
|||
{ |
|||
grantedConsent = new ConsentResponse |
|||
{ |
|||
RememberConsent = ConsentInput.RememberConsent, |
|||
ScopesConsented = ConsentInput.GetAllowedScopeNames() |
|||
}; |
|||
} |
|||
else |
|||
{ |
|||
throw new UserFriendlyException("You must pick at least one permission"); //TODO: How to handle this
|
|||
} |
|||
} |
|||
|
|||
if (grantedConsent != null) |
|||
{ |
|||
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
|||
if (request == null) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
await _interaction.GrantConsentAsync(request, grantedConsent); |
|||
|
|||
result.RedirectUri = ReturnUrl; //TODO: ReturnUrlHash?
|
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) |
|||
{ |
|||
return new ConsentModel.ScopeViewModel |
|||
{ |
|||
Name = identity.Name, |
|||
DisplayName = identity.DisplayName, |
|||
Description = identity.Description, |
|||
Emphasize = identity.Emphasize, |
|||
Required = identity.Required, |
|||
Checked = check || identity.Required |
|||
}; |
|||
} |
|||
|
|||
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(Scope scope, 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 |
|||
}; |
|||
} |
|||
|
|||
protected virtual ConsentModel.ScopeViewModel GetOfflineAccessScope(bool check) |
|||
{ |
|||
return new ConsentModel.ScopeViewModel |
|||
{ |
|||
Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, |
|||
DisplayName = "Offline Access", //TODO: Localize
|
|||
Description = "Access to your applications and resources, even when you are offline", |
|||
Emphasize = true, |
|||
Checked = check |
|||
}; |
|||
} |
|||
|
|||
public class ConsentInputModel |
|||
{ |
|||
public List<ConsentModel.ScopeViewModel> IdentityScopes { get; set; } |
|||
|
|||
public List<ConsentModel.ScopeViewModel> ApiScopes { get; set; } |
|||
|
|||
[Required] |
|||
public string UserDecision { get; set; } |
|||
|
|||
public bool RememberConsent { get; set; } |
|||
|
|||
public List<string> GetAllowedScopeNames() |
|||
{ |
|||
return IdentityScopes.Union(ApiScopes).Where(s => s.Checked).Select(s => s.Name).ToList(); |
|||
} |
|||
} |
|||
|
|||
public class ScopeViewModel |
|||
{ |
|||
[Required] |
|||
[HiddenInput] |
|||
public string Name { get; set; } |
|||
|
|||
public bool Checked { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public string Description { get; set; } |
|||
|
|||
public bool Emphasize { get; set; } |
|||
|
|||
public bool Required { get; set; } |
|||
} |
|||
|
|||
public class ProcessConsentResult |
|||
{ |
|||
public bool IsRedirect => RedirectUri != null; |
|||
public string RedirectUri { 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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue