fix(management): invalidate RoleMappingCache after role-mapping and scope-rule mutations commit (review 2026-10-21 lane K M1)

This commit is contained in:
Joseph Doherty
2026-10-22 09:26:54 -04:00
parent d495494074
commit df1f4efcfa
3 changed files with 108 additions and 0 deletions
@@ -2820,6 +2820,21 @@ public class ManagementActor : ReceiveActor
$"Role '{role}' is not a recognized role. Valid roles are: {string.Join(", ", Roles.All)}.");
}
/// <summary>
/// Drops this node's <see cref="RoleMappingCache"/> snapshot once a mapping / scope-rule
/// change has committed, so the next role mapping on this node sees it immediately rather
/// than after the cache TTL (lane K review M1). Queued post-commit: invalidating before the
/// commit would let a concurrent mapping re-cache the pre-change table. Other central
/// nodes still converge within <c>RoleMappingCacheSeconds</c>.
/// </summary>
private static Task InvalidateRoleMappingCacheAfterCommit(IServiceProvider sp)
{
var cache = sp.GetService<RoleMappingCache>();
return cache is null
? Task.CompletedTask
: AfterConfigurationCommit(sp, () => { cache.Invalidate(); return Task.CompletedTask; });
}
private static async Task<object?> HandleCreateRoleMapping(IServiceProvider sp, CreateRoleMappingCommand cmd, string user)
{
var role = CanonicalizeMappingRole(cmd.Role);
@@ -2827,6 +2842,7 @@ public class ManagementActor : ReceiveActor
var mapping = new LdapGroupMapping(cmd.LdapGroupName, role);
await repo.AddMappingAsync(mapping);
await repo.SaveChangesAsync();
await InvalidateRoleMappingCacheAfterCommit(sp);
await AuditAsync(sp, user, "Create", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
return mapping;
}
@@ -2841,6 +2857,7 @@ public class ManagementActor : ReceiveActor
mapping.Role = role;
await repo.UpdateMappingAsync(mapping);
await repo.SaveChangesAsync();
await InvalidateRoleMappingCacheAfterCommit(sp);
await AuditAsync(sp, user, "Update", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
return mapping;
}
@@ -2850,6 +2867,7 @@ public class ManagementActor : ReceiveActor
var repo = sp.GetRequiredService<ISecurityRepository>();
await repo.DeleteMappingAsync(cmd.MappingId);
await repo.SaveChangesAsync();
await InvalidateRoleMappingCacheAfterCommit(sp);
await AuditAsync(sp, user, "Delete", "RoleMapping", cmd.MappingId.ToString(), cmd.MappingId.ToString(), null);
return true;
}
@@ -3807,6 +3825,7 @@ public class ManagementActor : ReceiveActor
var rule = new SiteScopeRule { LdapGroupMappingId = cmd.MappingId, SiteId = cmd.SiteId };
await repo.AddScopeRuleAsync(rule);
await repo.SaveChangesAsync();
await InvalidateRoleMappingCacheAfterCommit(sp);
await AuditAsync(sp, user, "Create", "ScopeRule", rule.Id.ToString(), $"Mapping:{cmd.MappingId}/Site:{cmd.SiteId}", rule);
return rule;
}
@@ -3816,6 +3835,7 @@ public class ManagementActor : ReceiveActor
var repo = sp.GetRequiredService<ISecurityRepository>();
await repo.DeleteScopeRuleAsync(cmd.ScopeRuleId);
await repo.SaveChangesAsync();
await InvalidateRoleMappingCacheAfterCommit(sp);
await AuditAsync(sp, user, "Delete", "ScopeRule", cmd.ScopeRuleId.ToString(), cmd.ScopeRuleId.ToString(), null);
return true;
}