Skip to content

RBAC Phase 4: Documentation & Final Testing #108

@kevalyq

Description

@kevalyq

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 Entity with 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
  • 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 roles
      • direct: Permissions assigned directly to user
      • all: 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"]
      }
  • 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 RoleManagementPolicy
    • create: Admin only
    • update: Admin only
    • delete: Admin only (blocked if role assigned to users)
  • Create PermissionManagementPolicy
    • All actions: Admin only
  • Create UserPermissionPolicy
    • assignPermission: Admin only
    • revokePermission: Admin only
    • viewPermissions: User can view own, Admin can view all
  • Create form requests:
    • CreateRoleRequest
    • UpdateRoleRequest
    • CreatePermissionRequest
    • UpdatePermissionRequest
    • AssignUserPermissionRequest

Database Changes

  • Optional: Add migration: add_temporal_columns_to_model_has_permissions_table
    • valid_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.action naming 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 guide
  • docs/guides/temporal-roles.md - Temporal role patterns
  • docs/guides/permission-system.md - Permission naming and organization
  • docs/guides/direct-permissions.md - When and how to use direct permissions
  • database/seeders/RolesAndPermissionsSeeder.php
  • Updated README.md with RBAC overview
  • Updated CHANGELOG.md with v0.2.0 changes

API Examples

Role Management

List all roles:

GET /api/v1/roles

Create 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 2025

Revoke direct permission:

DELETE /api/v1/users/123/permissions/employees.export
# ✅ Removes direct permission only
# ℹ️ Role-based permissions remain unchanged

View 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 management
  • shifts - Shift planning
  • work_instructions - Dienstanweisungen
  • roles - Role management
  • permissions - Permission management
  • works_council - Works council specific
  • reports - Report generation

Common Actions:

  • read - View resource
  • create - Create new resource
  • update - Modify existing resource
  • delete - Remove resource
  • export - Export data
  • * - All actions on resource (shorthand)

Special Actions:

  • employees.read_salary - View salary data
  • employees.read_all_branches - Cross-branch access
  • employees.export - Export employee data
  • shifts.publish - Publish shift plans
  • shifts.approve_as_br - Works council approval
  • roles.assign_temporary - Assign temporal roles
  • roles.extend_expiration - Extend role expiration
  • reports.generate - Generate reports
  • reports.export - Export reports

Reference

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:

  1. Simplicity - One deletion rule for all roles
  2. Flexibility - Everything manageable via UI/API
  3. Idempotent Seeder - Deleted roles are recreated automatically
  4. 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

No one assigned

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions