Add service-layer auth enforcement for role assignment#210
Conversation
Push authorization checks into RoleAssignmentService so role mutations are protected regardless of call path (controller, background job, API). Service methods now accept ClaimsPrincipal and call IAuthorizationService before executing. Controllers delegate to the service instead of checking RoleChecks.CanManageRole directly. SystemPrincipal provides a system-level auth context for background jobs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Release Review — Issues Found
Since this PR is centralizing role-assignment enforcement, I would fix that mismatch and add coverage for the missing roles before releasing it to production. |
PR Review — 2026-04-10PR: #210 — Add service-layer auth enforcement for role assignment (link) Prior Review Status
Spec ComplianceIssue nobodies-collective#418: Add service-layer auth enforcement: role assignment and removal (OPEN)
Code QualityCRITICAL1. Role matrix contradiction — Four sources say Board/HumanAdmin can manage CampAdmin, TicketAdmin, NoInfoAdmin:
But Decision needed: expand IMPORTANT2. Fetches the 3. Handler in Web layer; service in Infrastructure — The service ( 4. Test file in wrong project — Tests a Web-layer class but lives in Application.Tests. Should move to match the handler's project. MINOR5. Auth-denial logs omit principal identity — Warning logs include role name and target user but not the denied principal's identity. Add 6. Missing test cases for CampAdmin/TicketAdmin/NoInfoAdmin — Once finding #1 is resolved, add theory cases for these roles (whether they should pass or fail). Clean Areas
Bottom LineThe PR fully addresses issue nobodies-collective#418 — all four acceptance criteria are met and the structural approach is solid. The critical blocker is the role matrix contradiction (finding #1): this PR codifies the narrower Review by Claude Code |
…logging - Add CampAdmin, TicketAdmin, NoInfoAdmin to CanManageRole for Board/HumanAdmin, matching BoardAssignableRoles and documented behavior - Move RoleAssignmentAuthorizationHandler from Web to Application layer with HashSet-based BoardManageableRoles (no dependency on Web RoleChecks) - EndRoleAsync returns generic NotFound for auth denial to prevent enumeration - Add principal identity to auth-denial log messages in both Assign and EndRole - Add CampAdmin/TicketAdmin/NoInfoAdmin test cases for Board and HumanAdmin - Update EndRoleAsync test to expect NotFound instead of Unauthorized Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes Applied — 2026-04-10Requested: Fix all 6 findings from the PR review. Changes (commit e3ed627):
Verification: Build clean (0 errors, 0 warnings). All 1025 tests pass. Note: Fix & re-review by Claude Code |
…ervice → IAuthorizationService → TeamAuthorizationHandler → ITeamService) TeamAuthorizationHandler now lazily resolves ITeamService via IServiceProvider instead of constructor injection, breaking the cycle introduced by #210. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ive#420) Phase 3c (final slice) of the first-class authorization transition plan. Budget mutations are now protected at the service boundary regardless of call path (controllers, background jobs, future API surfaces), mirroring the pattern established by #210 (role assignment). - BudgetOperationRequirement moves to Humans.Application.Authorization and gains a resource-free Manage static alongside the existing Edit. - New BudgetManageAuthorizationHandler succeeds for Admin / FinanceAdmin / SystemPrincipal on the Manage requirement (used for budget years, groups, categories, ticketing projection parameters, and ticketing sync jobs). - Existing BudgetAuthorizationHandler still handles Edit against a BudgetCategory resource (department-coordinator scoping) and now succeeds for SystemPrincipal. Uses IServiceProvider to lazily resolve IBudgetService, matching TeamAuthorizationHandler so the new BudgetService -> IAuthorizationService dependency does not introduce a DI cycle. - Every IBudgetService mutation method now accepts a ClaimsPrincipal and calls IAuthorizationService.AuthorizeAsync before touching the database. Unauthorized callers raise UnauthorizedAccessException. Line-item create/update/delete authorize against the loaded BudgetCategory (Edit); year/group/category/projection/sync mutations authorize against null (Manage). - BudgetController and FinanceController forward User to the service; the controller-level [Authorize(Policy = FinanceAdminOrAdmin)] and resource-based edit check stay as defense in depth. - TicketingBudgetService and ITicketingBudgetService forward ClaimsPrincipal through to BudgetService. TicketingBudgetSyncJob and DevelopmentBudgetSeeder pass SystemPrincipal.Instance. - RoleChecks is now an internal helper — part of the Phase 1 cleanup called out in docs/plans/2026-04-03-first-class-authorization-transition.md. ViewPolicies was already removed in earlier phases (no references remain outside that plan doc). Tests: - BudgetServiceTests updated for the new ctor, plus new cases covering unauthorized CreateLineItem / UpdateLineItem / DeleteLineItem / CreateYear / UpdateYearStatus / CreateGroup / CreateCategory paths (UnauthorizedAccessException thrown, no DB mutation) and an authorized FinanceAdmin path. - BudgetAuthorizationHandlerTests updated for the IServiceProvider constructor and exercises the new SystemPrincipal override plus the "Manage requirement not handled" guard. - New BudgetManageAuthorizationHandlerTests covers Admin, FinanceAdmin, SystemPrincipal, Board (denied), regular user (denied), unauthenticated user (denied), and the "Edit requirement not handled" guard. - docs/sections/Budget.md gains an Authorization note describing the two-layer defense. Closes nobodies-collective#420 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
RoleAssignmentServiceso role assignment/removal is protected regardless of call path (controller, background job, future API)ClaimsPrincipaland callIAuthorizationServicewith a newRoleAssignmentOperationRequirementbefore executing mutationsSystemPrincipalhelper providing a system-level auth context for background jobs, withRoleAssignmentAuthorizationHandlerthat grants system principal full accessIssues
Test plan
RoleAssignmentAuthorizationHandlerTests: 18 test cases covering Admin override, Board/HumanAdmin per-role access, system principal bypass, and denial for unauthorized usersRoleAssignmentServiceTests: New tests for authorization denial onAssignRoleAsyncandEndRoleAsync, and system principal forwarding verification