Inits the workflow configuration
This commit is contained in:
parent
f66dac31c9
commit
e6e098b32e
286 changed files with 33660 additions and 64632 deletions
48
src/Yavsc/Pages/Ciba/All.cshtml
Normal file
48
src/Yavsc/Pages/Ciba/All.cshtml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@page
|
||||
@model Yavsc.Pages.Ciba.AllModel
|
||||
@{
|
||||
}
|
||||
|
||||
<div class="ciba-page">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Pending Backchannel Login Requests</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Model.Logins.Any())
|
||||
{
|
||||
<table class="table table-bordered table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Client Id</th>
|
||||
<th>Binding Message</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var login in Model.Logins)
|
||||
{
|
||||
<tr>
|
||||
<td>@login.InternalId</td>
|
||||
<td>@login.Client.ClientId</td>
|
||||
<td>@login.BindingMessage</td>
|
||||
<td>
|
||||
<a asp-page="Consent" asp-route-id="@login.InternalId" class="btn btn-primary">Process</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>No Pending Login Requests</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
28
src/Yavsc/Pages/Ciba/All.cshtml.cs
Normal file
28
src/Yavsc/Pages/Ciba/All.cshtml.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
[SecurityHeaders]
|
||||
[Authorize]
|
||||
public class AllModel : PageModel
|
||||
{
|
||||
public IEnumerable<BackchannelUserLoginRequest> Logins { get; set; } = default!;
|
||||
|
||||
private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction;
|
||||
|
||||
public AllModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService)
|
||||
{
|
||||
_backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService;
|
||||
}
|
||||
|
||||
public async Task OnGet()
|
||||
{
|
||||
Logins = await _backchannelAuthenticationInteraction.GetPendingLoginRequestsForCurrentUserAsync();
|
||||
}
|
||||
}
|
||||
98
src/Yavsc/Pages/Ciba/Consent.cshtml
Normal file
98
src/Yavsc/Pages/Ciba/Consent.cshtml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
@page
|
||||
@model Yavsc.Pages.Ciba.Consent
|
||||
@{
|
||||
}
|
||||
|
||||
<div class="ciba-consent">
|
||||
<div class="lead">
|
||||
@if (Model.View.ClientLogoUrl != null)
|
||||
{
|
||||
<div class="client-logo"><img src="@Model.View.ClientLogoUrl"></div>
|
||||
}
|
||||
<h1>
|
||||
@Model.View.ClientName
|
||||
<small class="text-muted">is requesting your permission</small>
|
||||
</h1>
|
||||
|
||||
<h3>Verify that this identifier matches what the client is displaying: <em class="text-primary">@Model.View.BindingMessage</em></h3>
|
||||
|
||||
<p>Uncheck the permissions you do not wish to grant.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<partial name="_ValidationSummary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-page="/Ciba/Consent">
|
||||
<input type="hidden" asp-for="Input.Id" />
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
@if (Model.View.IdentityScopes.Any())
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
Personal Information
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@foreach (var scope in Model.View.IdentityScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="@scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.View.ApiScopes.Any())
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="glyphicon glyphicon-tasks"></span>
|
||||
Application Access
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@foreach (var scope in Model.View.ApiScopes)
|
||||
{
|
||||
<partial name="_ScopeListItem" model="scope" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
Description
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input class="form-control" placeholder="Description or name of device" asp-for="Input.Description" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<button name="Input.button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
|
||||
<button name="Input.button" value="no" class="btn btn-secondary">No, Do Not Allow</button>
|
||||
</div>
|
||||
<div class="col-sm-4 col-lg-auto">
|
||||
@if (Model.View.ClientUrl != null)
|
||||
{
|
||||
<a class="btn btn-outline-info" href="@Model.View.ClientUrl">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
<strong>@Model.View.ClientName</strong>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
228
src/Yavsc/Pages/Ciba/Consent.cshtml.cs
Normal file
228
src/Yavsc/Pages/Ciba/Consent.cshtml.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Events;
|
||||
using Duende.IdentityServer.Extensions;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Duende.IdentityServer.Validation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
[Authorize]
|
||||
[SecurityHeaders]
|
||||
public class Consent : PageModel
|
||||
{
|
||||
private readonly IBackchannelAuthenticationInteractionService _interaction;
|
||||
private readonly IEventService _events;
|
||||
private readonly ILogger<Consent> _logger;
|
||||
|
||||
public Consent(
|
||||
IBackchannelAuthenticationInteractionService interaction,
|
||||
IEventService events,
|
||||
ILogger<Consent> logger)
|
||||
{
|
||||
_interaction = interaction;
|
||||
_events = events;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ViewModel View { get; set; } = default!;
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; } = default!;
|
||||
|
||||
public async Task<IActionResult> OnGet(string? id)
|
||||
{
|
||||
if (!await SetViewModelAsync(id))
|
||||
{
|
||||
return RedirectToPage("/Home/Error/Index");
|
||||
}
|
||||
|
||||
Input = new InputModel
|
||||
{
|
||||
Id = id
|
||||
};
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost()
|
||||
{
|
||||
// validate return url is still valid
|
||||
var request = await _interaction.GetLoginRequestByInternalIdAsync(Input.Id ?? throw new ArgumentNullException(nameof(Input.Id)));
|
||||
if (request == null || request.Subject.GetSubjectId() != User.GetSubjectId())
|
||||
{
|
||||
_logger.InvalidId(Input.Id);
|
||||
return RedirectToPage("/Home/Error/Index");
|
||||
}
|
||||
|
||||
CompleteBackchannelLoginRequest? result = null;
|
||||
|
||||
// user clicked 'no' - send back the standard 'access_denied' response
|
||||
if (Input.Button == "no")
|
||||
{
|
||||
result = new CompleteBackchannelLoginRequest(Input.Id);
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
|
||||
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName));
|
||||
}
|
||||
// user clicked 'yes' - validate the data
|
||||
else if (Input.Button == "yes")
|
||||
{
|
||||
// if the user consented to some scope, build the response model
|
||||
if (Input.ScopesConsented.Any())
|
||||
{
|
||||
var scopes = Input.ScopesConsented;
|
||||
if (ConsentOptions.EnableOfflineAccess == false)
|
||||
{
|
||||
scopes = scopes.Where(x => x != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess);
|
||||
}
|
||||
|
||||
result = new CompleteBackchannelLoginRequest(Input.Id)
|
||||
{
|
||||
ScopesValuesConsented = scopes.ToArray(),
|
||||
Description = Input.Description
|
||||
};
|
||||
|
||||
// emit event
|
||||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false));
|
||||
Telemetry.Metrics.ConsentGranted(request.Client.ClientId, result.ScopesValuesConsented, false);
|
||||
var denied = request.ValidatedResources.ParsedScopes.Select(s => s.ParsedName).Except(result.ScopesValuesConsented);
|
||||
Telemetry.Metrics.ConsentDenied(request.Client.ClientId, denied);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError("", ConsentOptions.MustChooseOneErrorMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError("", ConsentOptions.InvalidSelectionErrorMessage);
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
// communicate outcome of consent back to identityserver
|
||||
await _interaction.CompleteLoginRequestAsync(result);
|
||||
|
||||
return RedirectToPage("/Ciba/All");
|
||||
}
|
||||
|
||||
// we need to redisplay the consent UI
|
||||
if (!await SetViewModelAsync(Input.Id))
|
||||
{
|
||||
return RedirectToPage("/Home/Error/Index");
|
||||
}
|
||||
return Page();
|
||||
}
|
||||
|
||||
private async Task<bool> SetViewModelAsync(string? id)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
|
||||
var request = await _interaction.GetLoginRequestByInternalIdAsync(id);
|
||||
if (request != null && request.Subject.GetSubjectId() == User.GetSubjectId())
|
||||
{
|
||||
View = CreateConsentViewModel(request);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.NoMatchingBackchannelLoginRequest(id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ViewModel CreateConsentViewModel(BackchannelUserLoginRequest request)
|
||||
{
|
||||
var vm = new ViewModel
|
||||
{
|
||||
ClientName = request.Client.ClientName ?? request.Client.ClientId,
|
||||
ClientUrl = request.Client.ClientUri,
|
||||
ClientLogoUrl = request.Client.LogoUri,
|
||||
BindingMessage = request.BindingMessage
|
||||
};
|
||||
|
||||
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources
|
||||
.Select(x => CreateScopeViewModel(x, Input == null || Input.ScopesConsented.Contains(x.Name)))
|
||||
.ToArray();
|
||||
|
||||
var resourceIndicators = request.RequestedResourceIndicators ?? Enumerable.Empty<string>();
|
||||
var apiResources = request.ValidatedResources.Resources.ApiResources.Where(x => resourceIndicators.Contains(x.Name));
|
||||
|
||||
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, Input == null || Input.ScopesConsented.Contains(parsedScope.RawValue));
|
||||
scopeVm.Resources = apiResources.Where(x => x.Scopes.Contains(parsedScope.ParsedName))
|
||||
.Select(x => new ResourceViewModel
|
||||
{
|
||||
Name = x.Name,
|
||||
DisplayName = x.DisplayName ?? x.Name,
|
||||
}).ToArray();
|
||||
apiScopes.Add(scopeVm);
|
||||
}
|
||||
}
|
||||
if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess)
|
||||
{
|
||||
apiScopes.Add(GetOfflineAccessScope(Input == null || Input.ScopesConsented.Contains(Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess)));
|
||||
}
|
||||
vm.ApiScopes = apiScopes;
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
|
||||
{
|
||||
return new ScopeViewModel
|
||||
{
|
||||
Name = identity.Name,
|
||||
Value = identity.Name,
|
||||
DisplayName = identity.DisplayName ?? identity.Name,
|
||||
Description = identity.Description,
|
||||
Emphasize = identity.Emphasize,
|
||||
Required = identity.Required,
|
||||
Checked = check || identity.Required
|
||||
};
|
||||
}
|
||||
|
||||
private static ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check)
|
||||
{
|
||||
var displayName = apiScope.DisplayName ?? apiScope.Name;
|
||||
if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter))
|
||||
{
|
||||
displayName += ":" + parsedScopeValue.ParsedParameter;
|
||||
}
|
||||
|
||||
return new ScopeViewModel
|
||||
{
|
||||
Name = parsedScopeValue.ParsedName,
|
||||
Value = parsedScopeValue.RawValue,
|
||||
DisplayName = displayName,
|
||||
Description = apiScope.Description,
|
||||
Emphasize = apiScope.Emphasize,
|
||||
Required = apiScope.Required,
|
||||
Checked = check || apiScope.Required
|
||||
};
|
||||
}
|
||||
|
||||
private static ScopeViewModel GetOfflineAccessScope(bool check)
|
||||
{
|
||||
return new ScopeViewModel
|
||||
{
|
||||
Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||
DisplayName = ConsentOptions.OfflineAccessDisplayName,
|
||||
Description = ConsentOptions.OfflineAccessDescription,
|
||||
Emphasize = true,
|
||||
Checked = check
|
||||
};
|
||||
}
|
||||
}
|
||||
14
src/Yavsc/Pages/Ciba/ConsentOptions.cs
Normal file
14
src/Yavsc/Pages/Ciba/ConsentOptions.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
public static class ConsentOptions
|
||||
{
|
||||
public static readonly bool EnableOfflineAccess = true;
|
||||
public static readonly string OfflineAccessDisplayName = "Offline Access";
|
||||
public static readonly string OfflineAccessDescription = "Access to your applications and resources, even when you are offline";
|
||||
|
||||
public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission";
|
||||
public static readonly string InvalidSelectionErrorMessage = "Invalid selection";
|
||||
}
|
||||
30
src/Yavsc/Pages/Ciba/Index.cshtml
Normal file
30
src/Yavsc/Pages/Ciba/Index.cshtml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@page
|
||||
@model Yavsc.Pages.Ciba.IndexModel
|
||||
@{
|
||||
}
|
||||
|
||||
<div class="ciba-page">
|
||||
<div class="lead">
|
||||
@if (Model.LoginRequest.Client.LogoUri != null)
|
||||
{
|
||||
<div class="client-logo"><img src="@Model.LoginRequest.Client.LogoUri"></div>
|
||||
}
|
||||
<h1>
|
||||
@Model.LoginRequest.Client.ClientName
|
||||
<small class="text-muted">is requesting your permission</small>
|
||||
</h1>
|
||||
|
||||
<h3>
|
||||
Verify that this identifier matches what the client is displaying:
|
||||
<em class="text-primary">@Model.LoginRequest.BindingMessage</em>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Do you wish to continue?
|
||||
</p>
|
||||
<div>
|
||||
<a class="btn btn-primary" asp-page="/Ciba/Consent" asp-route-id="@Model.LoginRequest.InternalId">Yes, Continue</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
42
src/Yavsc/Pages/Ciba/Index.cshtml.cs
Normal file
42
src/Yavsc/Pages/Ciba/Index.cshtml.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
[AllowAnonymous]
|
||||
[SecurityHeaders]
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public BackchannelUserLoginRequest LoginRequest { get; set; } = default!;
|
||||
|
||||
private readonly IBackchannelAuthenticationInteractionService _backchannelAuthenticationInteraction;
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public IndexModel(IBackchannelAuthenticationInteractionService backchannelAuthenticationInteractionService, ILogger<IndexModel> logger)
|
||||
{
|
||||
_backchannelAuthenticationInteraction = backchannelAuthenticationInteractionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGet(string id)
|
||||
{
|
||||
var result = await _backchannelAuthenticationInteraction.GetLoginRequestByInternalIdAsync(id);
|
||||
if (result == null)
|
||||
{
|
||||
_logger.InvalidBackchannelLoginId(id);
|
||||
return RedirectToPage("/Home/Error/Index");
|
||||
}
|
||||
else
|
||||
{
|
||||
LoginRequest = result;
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
12
src/Yavsc/Pages/Ciba/InputModel.cs
Normal file
12
src/Yavsc/Pages/Ciba/InputModel.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
public string? Button { get; set; }
|
||||
public IEnumerable<string> ScopesConsented { get; set; } = new List<string>();
|
||||
public string? Id { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
34
src/Yavsc/Pages/Ciba/ViewModel.cs
Normal file
34
src/Yavsc/Pages/Ciba/ViewModel.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Yavsc.Pages.Ciba;
|
||||
|
||||
public class ViewModel
|
||||
{
|
||||
public string? ClientName { get; set; }
|
||||
public string? ClientUrl { get; set; }
|
||||
public string? ClientLogoUrl { get; set; }
|
||||
|
||||
public string? BindingMessage { get; set; }
|
||||
|
||||
public IEnumerable<ScopeViewModel> IdentityScopes { get; set; } = Enumerable.Empty<ScopeViewModel>();
|
||||
public IEnumerable<ScopeViewModel> ApiScopes { get; set; } = Enumerable.Empty<ScopeViewModel>();
|
||||
}
|
||||
|
||||
public class ScopeViewModel
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Value { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public bool Emphasize { get; set; }
|
||||
public bool Required { get; set; }
|
||||
public bool Checked { get; set; }
|
||||
public IEnumerable<ResourceViewModel> Resources { get; set; } = Enumerable.Empty<ResourceViewModel>();
|
||||
}
|
||||
|
||||
public class ResourceViewModel
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
47
src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml
Normal file
47
src/Yavsc/Pages/Ciba/_ScopeListItem.cshtml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
@using Yavsc.Pages.Ciba
|
||||
@model ScopeViewModel
|
||||
|
||||
<li class="list-group-item">
|
||||
<label>
|
||||
<input class="consent-scopecheck"
|
||||
type="checkbox"
|
||||
name="Input.ScopesConsented"
|
||||
id="scopes_@Model.Value"
|
||||
value="@Model.Value"
|
||||
checked="@Model.Checked"
|
||||
disabled="@Model.Required" />
|
||||
@if (Model.Required)
|
||||
{
|
||||
<input type="hidden"
|
||||
name="Input.ScopesConsented"
|
||||
value="@Model.Value" />
|
||||
}
|
||||
<strong>@Model.DisplayName</strong>
|
||||
@if (Model.Emphasize)
|
||||
{
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
}
|
||||
</label>
|
||||
@if (Model.Required)
|
||||
{
|
||||
<span><em>(required)</em></span>
|
||||
}
|
||||
@if (Model.Description != null)
|
||||
{
|
||||
<div class="consent-description">
|
||||
<label for="scopes_@Model.Value">@Model.Description</label>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Resources?.Any() == true)
|
||||
{
|
||||
<div class="consent-description">
|
||||
<label>Will be available to these resource servers:</label>
|
||||
<ul>
|
||||
@foreach (var resource in Model.Resources)
|
||||
{
|
||||
<li>@resource.DisplayName</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
Loading…
Add table
Add a link
Reference in a new issue