Skip to content

Commit

Permalink
Merge pull request #1499 from DuendeSoftware/brock/update-aspid-host
Browse files Browse the repository at this point in the history
more host UI updates to keep the main host and AspId host in sync
  • Loading branch information
josephdecock committed Dec 20, 2023
2 parents a53ccca + a28f1ef commit 3e5606f
Show file tree
Hide file tree
Showing 29 changed files with 949 additions and 60 deletions.
9 changes: 2 additions & 7 deletions hosts/AspNetIdentity/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "ExternalProvider is nested by design", Scope = "type", Target = "~T:IdentityServerHost.Pages.Login.ViewModel.ExternalProvider")]
// shared
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Main catches and logs all exceptions by design")]
[assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "Consistent with the IdentityServer APIs")]
[assembly: SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = "Consistent with the IdentityServer APIs")]
[assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "This namespace is just for organization, and won't be referenced elsewhere", Scope = "namespace", Target = "~N:IdentityServerHost.Pages.Error")]
[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Namespaces of pages are not likely to be used elsewhere, so there is little chance of confusion", Scope = "type", Target = "~T:IdentityServerHost.Pages.Extensions")]
[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Resources is only used for initialization, so there is little chance of confusion", Scope = "type", Target = "~T:IdentityServerHost.Configuration.Resources")]
[assembly: SuppressMessage("Performance", "CA1805:Do not initialize unnecessarily", Justification = "This is for clarity and consistency with the surrounding code", Scope = "member", Target = "~F:IdentityServerHost.Pages.Logout.LogoutOptions.AutomaticRedirectAfterSignOut")]
[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "No need for ConfigureAwait in ASP.NET Core application code, as there is no SynchronizationContext.")]


32 changes: 15 additions & 17 deletions hosts/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class Index : PageModel
_identityProviderStore = identityProviderStore;
_events = events;
}

public async Task<IActionResult> OnGet(string? returnUrl)
{
await BuildModelAsync(returnUrl);
Expand Down Expand Up @@ -86,7 +86,7 @@ public async Task<IActionResult> OnPost()
return this.LoadingPage(Input.ReturnUrl);
}

return Redirect(Input.ReturnUrl);
return Redirect(Input.ReturnUrl ?? "~/");
}
else
{
Expand Down Expand Up @@ -117,7 +117,7 @@ public async Task<IActionResult> OnPost()
}

// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(Input.ReturnUrl);
return Redirect(Input.ReturnUrl ?? "~/");
}

// request for a local page
Expand Down Expand Up @@ -146,7 +146,7 @@ public async Task<IActionResult> OnPost()
await BuildModelAsync(Input.ReturnUrl);
return Page();
}

private async Task BuildModelAsync(string? returnUrl)
{
Input = new InputModel
Expand All @@ -157,7 +157,7 @@ private async Task BuildModelAsync(string? returnUrl)
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
{
var local = context.IdP == IdentityServerConstants.LocalIdentityProvider;
var local = context.IdP == Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider;

// this is meant to short circuit the UI and only trigger the one external IdP
View = new ViewModel
Expand All @@ -169,7 +169,7 @@ private async Task BuildModelAsync(string? returnUrl)

if (!local)
{
View.ExternalProviders = new[] { new ViewModel.ExternalProvider { AuthenticationScheme = context.IdP } };
View.ExternalProviders = new[] { new ViewModel.ExternalProvider ( authenticationScheme: context.IdP ) };
}

return;
Expand All @@ -180,18 +180,18 @@ private async Task BuildModelAsync(string? returnUrl)
var providers = schemes
.Where(x => x.DisplayName != null)
.Select(x => new ViewModel.ExternalProvider
{
DisplayName = x.DisplayName ?? x.Name,
AuthenticationScheme = x.Name
}).ToList();
(
authenticationScheme: x.Name,
displayName: x.DisplayName ?? x.Name
)).ToList();

var dynamicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync())
.Where(x => x.Enabled)
.Select(x => new ViewModel.ExternalProvider
{
AuthenticationScheme = x.Scheme,
DisplayName = x.DisplayName
});
(
authenticationScheme: x.Scheme,
displayName: x.DisplayName ?? x.Scheme
));
providers.AddRange(dynamicSchemes);


Expand All @@ -202,9 +202,7 @@ private async Task BuildModelAsync(string? returnUrl)
allowLocal = client.EnableLocalLogin;
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Count != 0)
{
providers = providers.Where(provider =>
provider.AuthenticationScheme != null &&
client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
}
}

Expand Down
4 changes: 0 additions & 4 deletions hosts/AspNetIdentity/Pages/Account/Login/InputModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ public class InputModel
{
[Required]
public string? Username { get; set; }

[Required]
public string? Password { get; set; }

public bool RememberLogin { get; set; }

public string? ReturnUrl { get; set; }

public string? Button { get; set; }
}
8 changes: 7 additions & 1 deletion hosts/AspNetIdentity/Pages/Account/Login/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public class ViewModel

public class ExternalProvider
{
public ExternalProvider(string authenticationScheme, string? displayName = null)
{
AuthenticationScheme = authenticationScheme;
DisplayName = displayName;
}

public string? DisplayName { get; set; }
public string? AuthenticationScheme { get; set; }
public string AuthenticationScheme { get; set; }
}
}
1 change: 1 addition & 0 deletions hosts/AspNetIdentity/Pages/Account/Logout/LogoutOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


namespace IdentityServerHost.Pages.Logout;

public static class LogoutOptions
Expand Down
48 changes: 48 additions & 0 deletions hosts/AspNetIdentity/Pages/Ciba/All.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@page
@model IdentityServerHost.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 changes: 28 additions & 0 deletions hosts/AspNetIdentity/Pages/Ciba/All.cshtml.cs
Original file line number Diff line number Diff line change
@@ -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 IdentityServerHost.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 changes: 98 additions & 0 deletions hosts/AspNetIdentity/Pages/Ciba/Consent.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
@page
@model IdentityServerHost.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>
Loading

0 comments on commit 3e5606f

Please sign in to comment.