Child of PAR-182.
Today the backend authorizes nothing — security/ActorContext.java and api/ApiAuthFilter.java explicitly defer policy to the UI, so any holder of a valid workspace API key (or the static service key) can perform every admin write, demote an owner, or remove the last owner.
Fix: enforce permissions server-side, keyed off the resolved actor's role in the active tenant (TenantContext + the new role model).
tenant_member.role_id (after the role-model migration).WorkspaceAdminService, task mutations, releases, integrations) — e.g. requirePermission(MEMBER_MANAGE) before setMemberRole/removeMember; ISSUE_DELETE before delete; SETTINGS_MANAGE before status/label/role edits.setMemberRole/removeMember.Acceptance: a viewer/contributor key cannot perform manage-level writes; a non-owner cannot demote/remove an owner; the last owner cannot be removed. Service-layer tests per permission.
Done on develop (commit c7d4aa0). Enforcement now spans the whole REST surface: IssueApiController (issue.create/update/comment; delete → soft-archive on issue.delete), ReleaseApiController + IntegrationApiController (settings.manage), in addition to the admin surface from 25680a9. Staff decision implemented: a forwarded X-Actor-Staff actor resolves to admin-equivalent permissions in any workspace (logged), minus the owner-only powers (issue.destroy / workspace.manage). 108 tests green.
Verified LIVE. perms.require(...) enforced across every mutating surface — WorkspaceAdminService (member/role/settings/team/project/state/sprint), IssueApiController (create/update/move/reorder/delete→archive/comment), ReleaseApiController (cut/addIssue/removeIssue/freeze → settings.manage), IntegrationApiController (guild/keys → settings.manage), WorkspaceSettingsService. Last-owner guard + owner-immutability present; static key = full trust, per-workspace keys scoped to role. No unguarded mutating endpoint found. Acceptance met.
Partial — admin surface done on develop (commit
25680a9).PermissionServiceresolves the actor's grants fromActorContext+TenantContext;require(...)throwsForbiddenException→ 403. Enforced acrossWorkspaceAdminService:member.manageon add/set-role/remove member,settings.manageon labels/teams/projects/statuses. Owner protections: only an owner can grant the owner role or modify/remove another owner; last owner can't be demoted/removed. 7 RBAC tests added.Remaining for this issue:
IssueApiController(issue.create/update/delete),ReleaseApiController(release.manage),IntegrationApiController+ GitHub repo-link config (settings.manage),VoteService(stays open to any signed-in user by design).staffrole interacts: backend now denies non-members, so a staff user who isn't a workspace member will get 403 on managed writes (previously the UI let staff act on any workspace). Either add staff as members or add an explicit backend break-glass — coordinate with PAR-187.