test(authz): pin the editor role-floor on group pubsub + queue write caps

Mirrors group_kv_caps_resolve_by_role_up_the_chain for the two other
group write capabilities: a Viewer is denied GroupPubsubPublish and
GroupQueueEnqueue, an Editor is allowed, and an outsider is denied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-15 20:13:00 +02:00
parent 977abc25d0
commit 789325b6db

View File

@@ -1382,6 +1382,64 @@ mod tests {
);
}
/// §11.6 D2/D3: shared-topic PUBLISH and shared-queue ENQUEUE are writes, so
/// they require editor+ on the owning group. The three older group data caps
/// (KV/docs/files) have this test; these two — the newest, and the ones whose
/// blast radius is an entire subtree's handlers — did not. A `role_satisfies`
/// regression that let a Viewer publish/enqueue would go uncaught otherwise.
#[tokio::test]
async fn group_pubsub_and_queue_write_caps_require_editor() {
let repo = InMemoryAuthzRepo::default();
let root = GroupId::new();
let team = GroupId::new();
repo.add_group(root, None).await;
repo.add_group(team, Some(root)).await;
// A Viewer at root is denied BOTH writes on a descendant group.
let viewer = principal(InstanceRole::Member);
repo.grant_group(viewer.user_id, root, AppRole::Viewer)
.await;
for cap in [
Capability::GroupPubsubPublish(team),
Capability::GroupQueueEnqueue(team),
] {
assert_eq!(
can(&repo, &viewer, cap).await.unwrap(),
Decision::Deny,
"a Viewer must not publish/enqueue into a shared group collection"
);
}
// An Editor at root is allowed both (resolved up the chain).
let editor = principal(InstanceRole::Member);
repo.grant_group(editor.user_id, root, AppRole::Editor)
.await;
for cap in [
Capability::GroupPubsubPublish(team),
Capability::GroupQueueEnqueue(team),
] {
assert!(
can(&repo, &editor, cap).await.unwrap().is_allow(),
"an Editor on the owning group's ancestor must be allowed"
);
}
// An unrelated member gets neither.
let outsider = principal(InstanceRole::Member);
assert_eq!(
can(&repo, &outsider, Capability::GroupPubsubPublish(team))
.await
.unwrap(),
Decision::Deny
);
assert_eq!(
can(&repo, &outsider, Capability::GroupQueueEnqueue(team))
.await
.unwrap(),
Decision::Deny
);
}
#[tokio::test]
async fn group_docs_caps_resolve_by_role_up_the_chain() {
// §11.6 docs: same trust shape as shared KV — read is viewer+, write is