yavsc-org: OAuth2 client admin editor overhaul — per-collection pages + missing fields
The OAuth2 client editor at /Client/Edit/{id} previously exposed 8
fields out of ~30 scalars and 10 collections on the IdentityServer8
Client entity. Editing the collections (RedirectUris, Scopes, Grant
Types, Cors Origins, IdP Restrictions, Claims, Properties, Secrets)
was either impossible or jammed into a single broken text input that
bound against an IEnumerable<string> property.
Restructure into per-collection subpages, each with its own
list/add/remove flow:
- RedirectUris /Client/EditRedirectUris/{id}
- PostLogoutRedirectUris /Client/EditPostLogoutRedirectUris/{id}
- Scopes /Client/EditScopes/{id}
- GrantTypes /Client/EditGrantTypes/{id}
- CorsOrigins /Client/EditCorsOrigins/{id}
- IdPRestrictions /Client/EditIdPRestrictions/{id}
- Claims /Client/EditClaims/{id}
- Properties /Client/EditProperties/{id}
- Secrets /Client/EditSecrets/{id}
Implementation:
- New partial class ClientController.Collections.cs with one
GET/Add/Remove trio per collection. Add/Remove dispatch through
generic helpers that handle the EF row + ClientId check.
- Shared _EditableStringList.cshtml partial consumed by the six
single-string-field collection pages. Uses reflection to pull
the value field and the row Id off the entity — avoids six
nearly-identical table+form copies.
- Claims / Properties / Secrets each have their own view because
they carry 2+ fields (Type+Value, Key+Value, or
Type+Value+Description+Expiration).
- Main Edit.cshtml enriched: ClientId/Id hidden, all scalar
fields split into fieldsets (Core, Security, Logout, Tokens,
Device flow, Tokens extra), nav links to the 9 subpages with
current row counts as badges.
- ClientController.Edit(int) GET now loads the client with all
navigations via LoadClientAsync so the Edit.cshtml nav badges
render real counts.
Field-correctness notes (verified by disassembling HigginsSoft
IdentityServer8.EntityFramework.Entities.Client 8.0.5-preview-net9):
- The property is PairWiseSubjectSalt, not PairwiseSubjectSalt
(capital W on 'Wise').
- CibaLifetime and PollingInterval do NOT exist on Client in this
IdentityServer8 version — those properties were a guess. The
Device flow fieldset contains DeviceCodeLifetime + UserCodeType
instead.
- AllowedIdentityTokenSigningAlgorithms and AllowAccessTokensViaBrowser
were missing from the original form and are now exposed.
- ConsentLifetime and UserSsoLifetime are int? (nullable); the form
binds them as plain int fields which accept empty strings.
Security:
- All new actions stay under [Authorize('AdministratorOnly')].
- Each Add/Remove takes an explicit id (Client.Id) and the row's
ClientId is checked on the server before any delete; a rowId
from another client returns NotFound.
Docs:
- doc/dev-tracking/client-editor-overhaul.md — inventory, status,
follow-up ideas (confirmation prompts, validation, MVC tests).
This commit is contained in:
parent
d62e59ba30
commit
ecac359344
14 changed files with 1255 additions and 31 deletions
77
src/Yavsc.Org/Views/Client/_EditableStringList.cshtml
Normal file
77
src/Yavsc.Org/Views/Client/_EditableStringList.cshtml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
@model IEnumerable<object>
|
||||
@using System.Reflection
|
||||
@*
|
||||
Shared partial for client collection pages whose row type carries a
|
||||
single string field plus an `Id` (used for Remove).
|
||||
|
||||
Inputs (via ViewData):
|
||||
- addAction: name of the Add action (e.g. "AddRedirectUri")
|
||||
- removeAction: name of the Remove action
|
||||
- rowKey: display label for the column header
|
||||
- valueField: name of the string property to display (e.g. "RedirectUri")
|
||||
- inputName: name attribute on the Add form's text input
|
||||
- clientId: passed through as a hidden field on both forms
|
||||
- placeholder: optional placeholder text
|
||||
|
||||
Each row is rendered with an "X" button that POSTs to removeAction with
|
||||
the row's `Id`. Add is a separate form with a single text input.
|
||||
*@
|
||||
|
||||
@{
|
||||
var addAction = ViewData["addAction"] as string ?? "Add";
|
||||
var removeAction = ViewData["removeAction"] as string ?? "Remove";
|
||||
var rowKey = ViewData["rowKey"] as string ?? "Value";
|
||||
var valueField = ViewData["valueField"] as string ?? "Value";
|
||||
var inputName = ViewData["inputName"] as string ?? "value";
|
||||
var clientId = ViewData["clientId"];
|
||||
var placeholder = ViewData["placeholder"] as string ?? string.Empty;
|
||||
var idProp = typeof(object).GetProperty("Id");
|
||||
}
|
||||
|
||||
@if (TempData["Error"] is string err)
|
||||
{
|
||||
<div class="alert alert-danger">@err</div>
|
||||
}
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>@rowKey</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr><td colspan="2" class="text-muted">—</td></tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var row in Model)
|
||||
{
|
||||
var value = row.GetType().GetProperty(valueField)?.GetValue(row) as string ?? string.Empty;
|
||||
var rowId = row.GetType().GetProperty("Id")?.GetValue(row);
|
||||
<tr>
|
||||
<td>@value</td>
|
||||
<td>
|
||||
<form asp-action="@removeAction" method="post" class="form-inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@clientId" />
|
||||
<input type="hidden" name="rowId" value="@rowId" />
|
||||
<button type="submit" class="btn btn-link btn-sm text-danger">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form asp-action="@addAction" method="post" class="form-inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@clientId" />
|
||||
<div class="form-group mr-2">
|
||||
<input type="text" name="@inputName" class="form-control" placeholder="@placeholder" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</form>
|
||||
Loading…
Reference in a new issue