fix(security): scope-filter secured-write listing + align spec with enforced site scoping (plan R2-07 T6)
This commit is contained in:
@@ -467,7 +467,7 @@ public class ManagementActor : ReceiveActor
|
||||
SubmitSecuredWriteCommand cmd => await HandleSubmitSecuredWrite(sp, cmd, user),
|
||||
ApproveSecuredWriteCommand cmd => await HandleApproveSecuredWrite(sp, cmd, user),
|
||||
RejectSecuredWriteCommand cmd => await HandleRejectSecuredWrite(sp, cmd, user),
|
||||
ListSecuredWritesCommand cmd => await HandleListSecuredWrites(sp, cmd),
|
||||
ListSecuredWritesCommand cmd => await HandleListSecuredWrites(sp, cmd, user),
|
||||
|
||||
// Transport bundle operations
|
||||
ExportBundleCommand cmd => await HandleExportBundle(sp, cmd, user.Username),
|
||||
@@ -1493,16 +1493,36 @@ public class ManagementActor : ReceiveActor
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleListSecuredWrites(
|
||||
IServiceProvider sp, ListSecuredWritesCommand cmd)
|
||||
IServiceProvider sp, ListSecuredWritesCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
// Site scoping (arch-review R2 N3): an explicit SiteId filter is scope-checked
|
||||
// like every other identifier-keyed command; an UNFILTERED list from a
|
||||
// site-scoped non-admin caller is constrained to their permitted sites at the
|
||||
// query level (rows AND totalCount both scoped — see ISecuredWriteRepository).
|
||||
IReadOnlyCollection<string>? permittedIdentifiers = null;
|
||||
if (cmd.SiteId is not null)
|
||||
{
|
||||
await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteId);
|
||||
}
|
||||
else if (user.PermittedSiteIds.Length > 0
|
||||
&& !user.Roles.Contains(Roles.Administrator, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var siteRepo = sp.GetRequiredService<ISiteRepository>();
|
||||
var permitted = new HashSet<string>(user.PermittedSiteIds);
|
||||
permittedIdentifiers = (await siteRepo.GetAllSitesAsync())
|
||||
.Where(s => permitted.Contains(s.Id.ToString()))
|
||||
.Select(s => s.SiteIdentifier)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var repo = sp.GetRequiredService<ISecuredWriteRepository>();
|
||||
|
||||
// Offset paging (arch-review P3): clamp untrusted paging inputs so a crafted
|
||||
// command can neither request a negative offset nor an unbounded page.
|
||||
var take = Math.Clamp(cmd.Take, 1, 500);
|
||||
var skip = Math.Max(0, cmd.Skip);
|
||||
var rows = await repo.QueryAsync(cmd.Status, cmd.SiteId, skip, take);
|
||||
var totalCount = await repo.CountAsync(cmd.Status, cmd.SiteId);
|
||||
var rows = await repo.QueryAsync(cmd.Status, cmd.SiteId, skip, take, permittedIdentifiers);
|
||||
var totalCount = await repo.CountAsync(cmd.Status, cmd.SiteId, permittedIdentifiers);
|
||||
|
||||
// Opportunistic expiry sweep (arch-review S2): every time the list is read, walk
|
||||
// the page and expire any overdue Pending row. TryExpireIfStaleAsync self-filters
|
||||
|
||||
Reference in New Issue
Block a user