refactor(acl): NodeAcl.LdapGroup -> Principal, with the proc that reads it (#520)
Stage 1a of the API-key ACL design. Behaviour-neutral: the trie walker already matched an opaque string, so only what produces that string changes. No enforcement is wired by this commit and none of the two live role gates move. Renamed through the ACL model: NodeAcl.Principal, TrieGrant.Principal, MatchedGrant.Principal, AuthorizationDecision.Principal, UserAuthorizationState.Principals, and PermissionTrie.CollectMatches's parameter. LdapGroupRoleMapping is a DIFFERENT entity that also has an LdapGroup column and is deliberately untouched — it backs AdminUI login, which keeps LDAP. The migration is the interesting part. EF scaffolds RenameColumn/RenameIndex, so rows survive — but it cannot know that dbo.sp_ComputeGenerationDiff checksums NodeAcl.LdapGroup. That proc is created in V3Initial.StoredProcedures and never redefined since, and SQL Server does not validate a procedure body when a column is renamed out from under it: the object keeps reporting healthy and fails at CALL time with Invalid column name, silently breaking config-drift detection. Verified against the live dev DB that the deployed proc really does reference the column. The migration therefore re-emits the proc, as one parameterised template shared by Up and Down so the two cannot drift. Prose that describes what is enforced TODAY (fleet-wide LDAP group -> role) is left alone, because it is still accurate until stage 2 lands. UserAuthorizationState's refresh machinery is retained unchanged but now carries a note that it is LDAP-shaped and largely vestigial here: an API-key principal set cannot change out of band, since keys and ACL rows both ride the deployment artifact, so the generation stamp rather than the freshness timer is the real invalidation signal. 41 authorization tests pass; solution builds clean. Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
@@ -52,8 +52,8 @@ else
|
||||
<InputText id="aid" @bind-Value="_form.NodeAclId" disabled="@(!IsNew)" class="form-control form-control-sm mono" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="grp">LDAP group</label>
|
||||
<InputText id="grp" @bind-Value="_form.LdapGroup" class="form-control form-control-sm mono"
|
||||
<label class="form-label" for="grp">Principal</label>
|
||||
<InputText id="grp" @bind-Value="_form.Principal" class="form-control form-control-sm mono"
|
||||
placeholder="cn=Operators,ou=FleetAdmin,dc=zb,dc=local" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ else
|
||||
_form = new FormModel
|
||||
{
|
||||
NodeAclId = _existing.NodeAclId,
|
||||
LdapGroup = _existing.LdapGroup,
|
||||
Principal = _existing.Principal,
|
||||
ScopeKind = _existing.ScopeKind,
|
||||
ScopeId = _existing.ScopeId,
|
||||
PermissionFlags = _existing.PermissionFlags,
|
||||
@@ -176,7 +176,7 @@ else
|
||||
{
|
||||
NodeAclId = _form.NodeAclId,
|
||||
ClusterId = ClusterId,
|
||||
LdapGroup = _form.LdapGroup,
|
||||
Principal = _form.Principal,
|
||||
ScopeKind = _form.ScopeKind,
|
||||
ScopeId = string.IsNullOrWhiteSpace(_form.ScopeId) ? null : _form.ScopeId,
|
||||
PermissionFlags = _form.PermissionFlags,
|
||||
@@ -188,7 +188,7 @@ else
|
||||
var entity = await db.NodeAcls.FirstOrDefaultAsync(a => a.ClusterId == ClusterId && a.NodeAclId == NodeAclId);
|
||||
if (entity is null) { _error = "Row no longer exists."; return; }
|
||||
db.Entry(entity).Property(e => e.RowVersion).OriginalValue = _form.RowVersion;
|
||||
entity.LdapGroup = _form.LdapGroup;
|
||||
entity.Principal = _form.Principal;
|
||||
entity.ScopeKind = _form.ScopeKind;
|
||||
entity.ScopeId = string.IsNullOrWhiteSpace(_form.ScopeId) ? null : _form.ScopeId;
|
||||
entity.PermissionFlags = _form.PermissionFlags;
|
||||
@@ -224,7 +224,7 @@ else
|
||||
private sealed class FormModel
|
||||
{
|
||||
[Required, RegularExpression("^[A-Za-z0-9_-]+$")] public string NodeAclId { get; set; } = "";
|
||||
[Required] public string LdapGroup { get; set; } = "";
|
||||
[Required] public string Principal { get; set; } = "";
|
||||
public NodeAclScopeKind ScopeKind { get; set; } = NodeAclScopeKind.Cluster;
|
||||
public string? ScopeId { get; set; }
|
||||
public NodePermissions PermissionFlags { get; set; } = NodePermissions.None;
|
||||
|
||||
@@ -33,7 +33,7 @@ else
|
||||
</div>
|
||||
|
||||
<section class="panel notice rise" style="animation-delay:.02s">
|
||||
ACL rows grant LDAP groups specific <span class="mono">NodePermissions</span> on a scope
|
||||
ACL rows grant principals specific <span class="mono">NodePermissions</span> on a scope
|
||||
(a folder, an equipment, a tag). Per-cluster role grants were dropped in favour of
|
||||
fleet-wide LDAP-group → role mapping; ACLs here are the finer-grained per-node scope.
|
||||
</section>
|
||||
@@ -51,7 +51,7 @@ else
|
||||
<thead>
|
||||
<tr>
|
||||
<th>NodeAclId</th>
|
||||
<th>LDAP group</th>
|
||||
<th>Principal</th>
|
||||
<th>Scope</th>
|
||||
<th>Scope target</th>
|
||||
<th>Permissions</th>
|
||||
@@ -64,7 +64,7 @@ else
|
||||
{
|
||||
<tr>
|
||||
<td><span class="mono small">@a.NodeAclId</span></td>
|
||||
<td><span class="mono">@a.LdapGroup</span></td>
|
||||
<td><span class="mono">@a.Principal</span></td>
|
||||
<td>@a.ScopeKind</td>
|
||||
<td><span class="mono small">@(a.ScopeId ?? "—")</span></td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user