-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Phase 4 Overview
Complete RBAC implementation with Role & Permission management API, Direct Permission assignment, comprehensive documentation, and final testing.
Tasks
Role & Permission Management API
Add CRUD endpoints for role and permission management with simple, unified approach - all roles are equal, only assignment status matters.
Role CRUD Endpoints
-
GET /api/v1/roles- List all roles- Include permission count and assigned user count
- Sort by name, creation date, or assigned users
-
POST /api/v1/roles- Create role- Validate: name unique, permissions exist
- Authorization: Admin only
-
GET /api/v1/roles/{id}- Get role details- Include assigned permissions
- Include assigned users count
-
PATCH /api/v1/roles/{id}- Update role- Update name and/or permissions
- Authorization: Admin only
-
DELETE /api/v1/roles/{id}- Delete role- Simple rule: Block deletion ONLY if role is assigned to users
- Authorization: Admin only
- Response if assigned:
422 Unprocessable Entitywith user count
-
POST /api/v1/roles/{id}/permissions- Assign permission to role- Bulk assignment support
- Authorization: Admin only
-
DELETE /api/v1/roles/{id}/permissions/{permission}- Revoke permission from role- Authorization: Admin only
Permission CRUD Endpoints
-
GET /api/v1/permissions- List all permissions- Group by resource (employees, shifts, work_instructions, etc.)
- Include assigned roles count
-
POST /api/v1/permissions- Create permission- Validate: name format
resource.action(e.g.,employees.read) - Authorization: Admin only
- Validate: name format
-
GET /api/v1/permissions/{id}- Get permission details- Include roles that have this permission
-
PATCH /api/v1/permissions/{id}- Update permission- Update description only (name immutable for security)
- Authorization: Admin only
-
DELETE /api/v1/permissions/{id}- Delete permission- Block if assigned to any role OR user directly
- Authorization: Admin only
User Direct Permission Assignment
Direct permissions bypass roles - assigned individually to specific users.
-
GET /api/v1/users/{id}/permissions- List all user permissions- Response includes:
via_roles: Permissions inherited from assigned rolesdirect: Permissions assigned directly to userall: Combined list (deduplicated)
- Example response:
{ "via_roles": ["employees.read", "shifts.read"], "direct": ["employees.export", "reports.generate"], "all": ["employees.read", "shifts.read", "employees.export", "reports.generate"] }
- Response includes:
-
POST /api/v1/users/{id}/permissions- Assign permission directly to user- Validate: permission exists
- Authorization: Admin only
- Bulk assignment support
- Optional temporal constraints:
valid_from(timestamp, nullable)valid_until(timestamp, nullable)
- Use case: Temporary special access without role change
-
DELETE /api/v1/users/{id}/permissions/{permission}- Revoke direct permission- Only removes direct assignment (role-based permissions remain)
- Authorization: Admin only
-
GET /api/v1/users/{id}/permissions/direct- List only direct permissions- Excludes permissions from roles
- Shows temporal constraints if present
Authorization & Validation
- Create
RoleManagementPolicycreate: Admin onlyupdate: Admin onlydelete: Admin only (blocked if role assigned to users)
- Create
PermissionManagementPolicy- All actions: Admin only
- Create
UserPermissionPolicyassignPermission: Admin onlyrevokePermission: Admin onlyviewPermissions: User can view own, Admin can view all
- Create form requests:
CreateRoleRequestUpdateRoleRequestCreatePermissionRequestUpdatePermissionRequestAssignUserPermissionRequest
Database Changes
- Optional: Add migration:
add_temporal_columns_to_model_has_permissions_tablevalid_from(timestamp, nullable)valid_until(timestamp, nullable)- Only if temporal direct permissions are needed
- Mirrors temporal role assignments
- Note: Can be added later if not needed initially
Tests
- Feature tests for Role CRUD (10 tests)
- List roles
- Create role (validation)
- Update role (name + permissions)
- Delete role (success when not assigned)
- Delete role (blocked when assigned to users)
- Permission assignment to role
- Feature tests for Permission CRUD (8 tests)
- List permissions (grouped by resource)
- Create permission (validation)
- Update permission (description only)
- Delete permission (blocked if assigned to role or user)
- Feature tests for User Permission Assignment (10 tests)
- List user permissions (via_roles + direct + all)
- Assign direct permission to user
- Revoke direct permission (role permissions remain)
- Direct permission with temporal constraints
- Permission inheritance (role + direct)
- View own permissions as user
- Admin can view any user's permissions
- Authorization tests (6 tests)
- Non-admin blocked from role management
- Non-admin blocked from permission management
- Non-admin blocked from assigning direct permissions
- User can view own permissions
- User cannot view other user's permissions
Seeder & Predefined Roles
- Create seeder for predefined roles
RolesAndPermissionsSeeder.php- Idempotent (uses
firstOrCreate- safe to run multiple times) - If role deleted → next seeder run recreates it
- Does not overwrite existing roles/permissions
- Predefined Roles:
- Admin - Full access to all resources
- Manager - Branch-scoped management (employees, shifts, work instructions)
- Guard - Own data + shift assignments
- Client - Read-only access to location data
- Works Council - BR-specific permissions (approval workflows, limited employee access)
- Permissions grouped by resource:
employees.*(read, create, update, delete, read_salary, read_all_branches, export)shifts.*(read, create, update, delete, publish, approve_as_br)work_instructions.*(read, create, update, delete, publish, acknowledge, view_acknowledgments)roles.*(read, create, update, delete, assign_temporary, extend_expiration)permissions.*(read, create, update, delete)works_council.*(access_employee_files, approve_shift_plans)reports.*(generate, view, export)
Documentation
- Update API documentation
- OpenAPI/Swagger spec for all endpoints (role assignment + role management + direct permissions)
- Temporal role assignment examples
- Role CRUD examples
- Permission management examples
- Direct permission assignment examples
- Permission inheritance diagrams (role + direct)
- Authorization flow diagrams
- Create role management guide
- How to create roles
- How to assign permissions to roles
- How to assign temporary roles to users
- How to extend/revoke roles
- When to use direct permissions vs roles
- Direct permission best practices
- Best practices for temporal assignments
- Document predefined roles
- Admin, Manager, Guard, Client, Works Council
- Permission matrix per role
- Scope limitations (branch, location)
- Seeder idempotency (roles recreated if deleted)
Integration & Performance Testing
- Write integration tests
- Full temporal role lifecycle
- Multi-user permission scenarios
- Cross-module authorization (employees, shifts, work instructions)
- Role creation and permission assignment
- Role deletion (blocked when assigned)
- Direct permission + role permission combination
- Permission inheritance resolution
- Performance testing
- Benchmark active role queries
- Test expiration command with 10k+ roles
- Verify index effectiveness
- Benchmark role/permission listing with 100+ roles
- Benchmark permission checking with direct + role permissions
Final Acceptance Testing
- Final acceptance testing
- All 4 use cases from Issue 🔐 Implement RBAC System (Role-Based Access Control) #5 validated
- Vacation coverage scenario
- Project-based access scenario
- Event elevation scenario
- Compliance (least privilege) scenario
- Role creation scenario
- Direct permission for temporary special access
- Permission revocation (direct vs role-based)
Acceptance Criteria
API Functionality
- All Role CRUD endpoints functional (7 routes)
- All Permission CRUD endpoints functional (5 routes)
- All User Permission endpoints functional (4 routes)
- Roles can be deleted ONLY when not assigned to users
- All roles fully manageable (no artificial restrictions)
- Permissions follow
resource.actionnaming convention - Direct permissions work independently of roles
- Permission inheritance (role + direct) works correctly
- Authorization policies enforced (Admin only for management)
Data Integrity
- Seeder creates 5 predefined roles with correct permissions
- Seeder is idempotent (can run multiple times safely)
- Deleted predefined roles are recreated on next seeder run
- All existing tests still pass (201 tests from Phase 3)
- New tests achieve ≥80% coverage (34 new tests minimum: 10 role + 8 permission + 10 user permission + 6 auth)
Documentation
- API documentation complete and published
- Role management guide written
- Predefined roles seeder created
- Integration tests validate full workflow
- Performance benchmarks documented
- All 8 use cases tested and verified
- README updated with RBAC section
- CHANGELOG.md updated with v0.2.0 changes
Documentation Deliverables
docs/api/rbac-endpoints.md- Complete API reference (assignment + management + direct permissions)docs/guides/role-management.md- How-to guidedocs/guides/temporal-roles.md- Temporal role patternsdocs/guides/permission-system.md- Permission naming and organizationdocs/guides/direct-permissions.md- When and how to use direct permissionsdatabase/seeders/RolesAndPermissionsSeeder.php- Updated
README.mdwith RBAC overview - Updated
CHANGELOG.mdwith v0.2.0 changes
API Examples
Role Management
List all roles:
GET /api/v1/rolesCreate role:
POST /api/v1/roles
{
"name": "Regional Manager",
"description": "Manages multiple branches in a region",
"permissions": ["employees.read", "employees.update", "shifts.read"]
}Update role:
PATCH /api/v1/roles/{role-id}
{
"name": "Senior Regional Manager",
"permissions": ["employees.*", "shifts.*", "work_instructions.read"]
}Delete role:
DELETE /api/v1/roles/{role-id}
# ✅ Success if not assigned to users
# ❌ 422 Unprocessable if assigned to users:
{
"error": "Cannot delete role while assigned to users",
"assigned_to": 15
}Permission Management
List permissions:
GET /api/v1/permissions
# Response grouped by resource:
{
"employees": ["employees.read", "employees.create", ...],
"shifts": ["shifts.read", "shifts.publish", ...],
...
}Create permission:
POST /api/v1/permissions
{
"name": "employees.export",
"description": "Export employee data to CSV/Excel"
}Direct Permission Assignment
View user's all permissions:
GET /api/v1/users/123/permissions
# Response:
{
"via_roles": [
{"name": "employees.read", "role": "Manager"},
{"name": "shifts.read", "role": "Manager"}
],
"direct": [
{"name": "employees.export", "valid_from": null, "valid_until": null},
{"name": "reports.generate", "valid_from": "2025-11-01", "valid_until": "2025-11-30"}
],
"all": ["employees.read", "shifts.read", "employees.export", "reports.generate"]
}Assign direct permission (permanent):
POST /api/v1/users/123/permissions
{
"permissions": ["employees.export", "reports.generate"]
}Assign direct permission (temporal):
POST /api/v1/users/123/permissions
{
"permissions": ["reports.generate"],
"valid_from": "2025-11-01T00:00:00Z",
"valid_until": "2025-11-30T23:59:59Z"
}
# Permission expires automatically after November 2025Revoke direct permission:
DELETE /api/v1/users/123/permissions/employees.export
# ✅ Removes direct permission only
# ℹ️ Role-based permissions remain unchangedView only direct permissions:
GET /api/v1/users/123/permissions/direct
# Response:
{
"direct": [
{"name": "employees.export", "assigned_at": "2025-11-09", "assigned_by": "admin@secpal.app"}
]
}Implementation Architecture
Permission Hierarchy
User Permissions = Role Permissions ∪ Direct Permissions
Example:
User "John" has role "Manager":
- Role "Manager" grants: [employees.read, employees.update, shifts.*]
- Direct permissions: [employees.export, reports.generate]
- Total permissions: [employees.read, employees.update, shifts.*, employees.export, reports.generate]
If "Manager" role is removed:
- Direct permissions remain: [employees.export, reports.generate]
Use Cases for Direct Permissions
| Scenario | Solution | Why Direct Permission? |
|---|---|---|
| Guard needs temporary export access | Assign employees.export directly for 1 week |
Don't want to modify Guard role or create new role |
| Manager should NOT delete employees | Revoke employees.delete directly |
Override role permission |
| Client needs report generation | Assign reports.generate directly |
Special exception for one client |
| Auditor needs read access | Assign multiple read permissions | Time-limited access without creating "Auditor" role |
Simple Role System
All roles are equal - no artificial distinction between "system" and "custom" roles.
Deletion Rule:
// Simple rule for ALL roles:
if ($role->users()->count() > 0) {
throw new ValidationException('Cannot delete role while assigned to users');
}
$role->delete(); // ✅ Works for ANY role (Admin, Manager, Custom, etc.)Predefined Roles via Seeder:
- Seeder creates: Admin, Manager, Guard, Client, Works Council
- Seeder is idempotent (uses
firstOrCreate) - If role deleted → next seeder run recreates it
- Roles are NOT protected at runtime - fully manageable via API/UI
Example Seeder:
class RolesAndPermissionsSeeder extends Seeder
{
public function run()
{
// Creates if not exists, skips if exists
$admin = Role::firstOrCreate(
['name' => 'Admin'],
['description' => 'Full system access']
);
// Sync permissions only if role has none
if ($admin->permissions()->count() === 0) {
$admin->syncPermissions(['*']);
}
// Repeat for Manager, Guard, Client, Works Council...
}
}Permission Naming Convention
Format: resource.action
Resources:
employees- Employee managementshifts- Shift planningwork_instructions- Dienstanweisungenroles- Role managementpermissions- Permission managementworks_council- Works council specificreports- Report generation
Common Actions:
read- View resourcecreate- Create new resourceupdate- Modify existing resourcedelete- Remove resourceexport- Export data*- All actions on resource (shorthand)
Special Actions:
employees.read_salary- View salary dataemployees.read_all_branches- Cross-branch accessemployees.export- Export employee datashifts.publish- Publish shift plansshifts.approve_as_br- Works council approvalroles.assign_temporary- Assign temporal rolesroles.extend_expiration- Extend role expirationreports.generate- Generate reportsreports.export- Export reports
Reference
- ADR-004: RBAC Architecture Decision
- Parent Issue: 🔐 Implement RBAC System (Role-Based Access Control) #5 (RBAC System)
- Depends on: Phase 1 ✅, Phase 2 ✅, Phase 3 ✅
Estimated Time
- 4-5 days
- Breakdown:
- Day 1: Seeder (idempotent), basic CRUD controllers
- Day 2: Authorization policies, validation, tests (Role/Permission)
- Day 3: Direct permission endpoints, validation, tests
- Day 4: Integration tests, performance tests, documentation
- Day 5: Final acceptance testing, README/CHANGELOG updates
Breaking Changes
None - This is purely additive functionality. All existing role assignment endpoints remain unchanged.
Design Decisions
Why No is_system_role Flag?
Decision: All roles are equal. No artificial distinction needed.
Rationale:
- ✅ Simplicity - One deletion rule for all roles
- ✅ Flexibility - Everything manageable via UI/API
- ✅ Idempotent Seeder - Deleted roles are recreated automatically
- ✅ No Confusion - No need to explain "system vs custom" to users
Protection:
- Roles protected by assignment status (cannot delete if assigned)
- Seeder ensures predefined roles always available (recreates if deleted)
- No runtime restrictions on modification (rename/change permissions freely)
This approach gives maximum flexibility while maintaining data integrity through simple, consistent rules.
Metadata
Metadata
Assignees
Type
Projects
Status