Skip to the content.

KMP RBAC Security Architecture Guide

Table of Contents

  1. Overview
  2. Core Architecture
  3. Security Components
  4. Validation Chain
  5. Permission Scoping
  6. Warrant Integration
  7. Policy Framework
  8. Implementation Examples
  9. Security Considerations
  10. Performance Optimization
  11. Troubleshooting

Overview

The Kingdom Management Portal (KMP) implements a sophisticated Role-Based Access Control (RBAC) system with temporal validation layers and warrant-based security enforcement. This system provides fine-grained authorization control across the hierarchical SCA organizational structure while maintaining security, performance, and flexibility.

Core Principles

Key Features


Core Architecture

RBAC Entity Relationship

erDiagram
    Members ||--o{ MemberRoles : "has roles"
    Roles ||--o{ MemberRoles : "assigned to"
    Roles ||--o{ RolePermissions : "has permissions"
    Permissions ||--o{ RolePermissions : "granted through"
    Permissions ||--o{ PermissionPolicies : "enhanced by"
    MemberRoles ||--o{ Warrants : "validated by"
    WarrantPeriods ||--o{ Warrants : "defines period"
    WarrantRosters ||--o{ Warrants : "batch managed"
    Branches ||--o{ MemberRoles : "scoped to"
    
    Members {
        int id PK
        string status
        date membership_expires_on
        date background_check_expires_on
        int birth_year
        int birth_month
        bool warrantable
    }
    
    Roles {
        int id PK
        string name UK
    }
    
    Permissions {
        int id PK
        string name
        string scoping_rule
        bool require_active_membership
        bool require_active_background_check
        int require_min_age
        bool requires_warrant
        bool is_super_user
    }
    
    MemberRoles {
        int id PK
        int member_id FK
        int role_id FK
        int branch_id FK
        date start_on
        date expires_on
        int approver_id FK
        int entity_id
        string entity_type
    }
    
    Warrants {
        int id PK
        int member_role_id FK
        int warrant_period_id FK
        date start_on
        date expires_on
        string status
    }

Authorization Flow

flowchart TD
    A[Permission Request] --> B[Identity Validation]
    B --> C[Load Member Permissions]
    C --> D[Role Assignment Check]
    D --> E[Temporal Validation]
    E --> F[Permission Requirements]
    F --> G[Warrant Validation]
    G --> H[Policy Framework]
    H --> I[Authorization Decision]
    
    D --> D1[Active Role?]
    E --> E1[Within Date Range?]
    F --> F1[Membership Valid?]
    F --> F2[Background Check?]
    F --> F3[Age Requirement?]
    G --> G1[Warrant Required?]
    G --> G2[Active Warrant?]
    H --> H1[Policy Method Exists?]
    H --> H2[Policy Logic Pass?]
    
    D1 -->|No| DENY[❌ Access Denied]
    E1 -->|No| DENY
    F1 -->|No| DENY
    F2 -->|No| DENY
    F3 -->|No| DENY
    G2 -->|No| DENY
    H2 -->|No| DENY
    
    I --> ALLOW[✅ Access Granted]

Security Components

1. Member Identity System

Entity: app/src/Model/Entity/Member.php

The Member entity serves as the identity foundation, implementing the KmpIdentityInterface for authentication and authorization integration.

Key Security Properties

// Member status validation - actual constants from source code
const STATUS_ACTIVE = 'active';
const STATUS_DEACTIVATED = 'deactivated';
const STATUS_VERIFIED_MEMBERSHIP = 'verified';
const STATUS_UNVERIFIED_MINOR = 'unverified minor';
const STATUS_MINOR_MEMBERSHIP_VERIFIED = '< 18 member verified';
const STATUS_MINOR_PARENT_VERIFIED = '< 18 parent verified';
const STATUS_VERIFIED_MINOR = 'verified < 18';

Critical Validation Points

2. Role Management System

Entities:

Roles serve as permission containers with time-bounded assignment capability.

Role Architecture

// Role entity core properties
class Role extends BaseEntity
{
    protected array $_accessible = [
        'name' => true,
        'permissions' => true,
        'members' => true
    ];
}

Association Structure

3. Permission System

Entities:

Permissions define atomic access rights with complex validation requirements.

Permission Types & Scoping

// Scoping constants from Permission entity
const SCOPE_GLOBAL = 'global';
const SCOPE_BRANCH_ONLY = 'branch_only';
const SCOPE_BRANCH_AND_CHILDREN = 'branch_and_children';

Validation Requirements

// Permission validation flags
$permission->require_active_membership    // SCA membership required
$permission->require_active_background_check  // Background check required
$permission->require_min_age             // Minimum age requirement
$permission->requires_warrant            // Active warrant required
$permission->is_super_user               // Super user permission
$permission->is_system                   // System-level permission

4. Warrant System (Temporal Validation Layer)

Entities:

Warrants provide an additional temporal validation layer for sensitive permissions.

Warrant Status Management

// Warrant status constants
const CURRENT_STATUS = 'current';
const PENDING_STATUS = 'pending';
const DECLINED_STATUS = 'declined';
const EXPIRED_STATUS = 'expired';
const CANCELLED_STATUS = 'cancelled';

Warrant Validation Logic


Validation Chain

Core Security Engine: PermissionsLoader

File: app/src/KMP/PermissionsLoader.php

The PermissionsLoader implements the core RBAC security engine with five-layer validation:

1. Member Identity Validation

// Identity verification in validPermissionClauses()
->where([
    'OR' => [
        'Permissions.require_active_membership' => false,
        'AND' => [
            'Members.status IN ' => [
                Member::STATUS_VERIFIED_MEMBERSHIP,  // 'verified'
                Member::STATUS_VERIFIED_MINOR,       // 'verified < 18'
                Member::STATUS_ACTIVE,               // 'active'
            ],
            'Members.membership_expires_on >' => DateTime::now(),
        ],
    ],
])

2. Role Assignment Temporal Validation

// Role assignment time bounds
->where([
    'MemberRoles.start_on < ' => DateTime::now(),
    'OR' => [
        'MemberRoles.expires_on IS ' => null,  // Permanent assignment
        'MemberRoles.expires_on >' => DateTime::now(),
    ],
])

3. Permission Requirement Validation

Background Check Validation:

->where([
    'OR' => [
        'Permissions.require_active_background_check' => false,
        'Members.background_check_expires_on >' => DateTime::now(),
    ],
])

Age-Based Access Control:

->where([
    'OR' => [
        'Permissions.require_min_age' => 0,
        'AND' => [
            'Members.birth_year = ' . strval($now->year) . ' - Permissions.require_min_age',
            'Members.birth_month <=' => $now->month,
        ],
        'Members.birth_year < ' . strval($now->year) . ' - Permissions.require_min_age',
    ],
])

4. Warrant Temporal Validation (Configurable)

// Configurable warrant validation
$useWarrant = StaticHelpers::getAppSetting('KMP.RequireActiveWarrantForSecurity');
if (strtolower($useWarrant) == 'yes') {
    $warrantSubquery = $warrantsTable->find()
        ->select(['Warrants.member_role_id'])
        ->where([
            'Warrants.start_on <' => $now,
            'Warrants.expires_on >' => $now,
            'Warrants.status' => Warrant::CURRENT_STATUS,
        ]);
        
    $q = $q->where([
        'OR' => [
            'Permissions.requires_warrant' => false,
            'AND' => [
                'Members.warrantable' => true,
                'MemberRoles.id IN' => $warrantSubquery,
            ],
        ],
    ]);
}

5. Policy Framework Integration

Dynamic Policy Discovery:

// From getApplicationPolicies() method
foreach ($paths as $policyPath) {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path)
    );
    foreach ($iterator as $file) {
        if ($file->isFile() && $file->getExtension() === 'php') {
            require_once $file->getPathname();
        }
    }
}

Permission Scoping

Branch-Based Security Model

The KMP system implements hierarchical permission scoping based on SCA organizational structure:

SCOPE_GLOBAL

// Global permission example
$permission = [
    'name' => 'System Administration',
    'scoping_rule' => Permission::SCOPE_GLOBAL,
    'branch_ids' => null,  // No branch restrictions
    'is_super_user' => true
];

SCOPE_BRANCH_ONLY

// Branch-only permission example
$permission = [
    'name' => 'Manage Local Events',
    'scoping_rule' => Permission::SCOPE_BRANCH_ONLY,
    'branch_ids' => [5, 12, 23],  // Specific branch IDs only
];

SCOPE_BRANCH_AND_CHILDREN

// Hierarchical permission processing in PermissionsLoader
case Permission::SCOPE_BRANCH_AND_CHILDREN:
    $descendants = $branchTable->getAllDecendentIds($branch_id);
    $descendants[] = $branch_id; // Include the branch itself
    $permissions[$permission->id]->branch_ids = $descendants;
    break;

Branch Hierarchy Integration

The branch scoping system integrates with the nested set model hierarchy:

// From BranchesTable - getAllDecendentIds() method
public function getAllDecendentIds(int $id): array
{
    $cacheKey = 'branch_descendants_' . $id;
    $cached = Cache::read($cacheKey, static::CACHE_CONFIG);
    if ($cached !== null) {
        return $cached;
    }
    
    // Query descendant branches using nested set model
    $branch = $this->get($id);
    $descendants = $this->find()
        ->where([
            'lft >' => $branch->lft,
            'rght <' => $branch->rght
        ])
        ->select(['id'])
        ->toArray();
        
    $ids = collection($descendants)->extract('id')->toArray();
    Cache::write($cacheKey, $ids, static::CACHE_CONFIG);
    return $ids;
}

Warrant Integration

Warrant as Temporal Validation Layer

Warrants provide an additional security layer for sensitive permissions, adding temporal validation beyond role assignments.

Warrant Lifecycle

stateDiagram-v2
    [*] --> Pending: Request Created
    Pending --> Current: Approved & Active
    Pending --> Declined: Rejected
    Current --> Expired: Time Boundary
    Current --> Cancelled: Administrative Action
    Declined --> [*]
    Expired --> [*]
    Cancelled --> [*]

Warrant-Secured Permission Flow

// From PermissionsLoader validPermissionClauses()
if (strtolower($useWarrant) == 'yes') {
    $warrantSubquery = $warrantsTable->find()
        ->select(['Warrants.member_role_id'])
        ->where([
            'Warrants.start_on <' => $now,           // Warrant has started
            'Warrants.expires_on >' => $now,         // Warrant hasn't expired  
            'Warrants.status' => Warrant::CURRENT_STATUS, // Warrant is active
        ]);
        
    $q = $q->where([
        'OR' => [
            'Permissions.requires_warrant' => false, // Permission doesn't require warrant
            'AND' => [
                'Members.warrantable' => true,              // Member is warrant-eligible
                'MemberRoles.id IN' => $warrantSubquery,    // Role has active warrant
            ],
        ],
    ]);
}

Configuration Control

// App setting in config/app.php
'KMP' => [
    'RequireActiveWarrantForSecurity' => 'yes' // Enable warrant validation
]

Warrant Management Components

WarrantPeriod Entity

WarrantRoster Entity

Warrant Processing Service

File: app/src/Services/WarrantManager/WarrantManagerInterface.php

interface WarrantManagerInterface
{
    public function requestWarrant(WarrantRequest $request): ServiceResult;
    public function approveWarrant(int $warrantId, int $approverId): ServiceResult;
    public function declineWarrant(int $warrantId, int $approverId, string $reason): ServiceResult;
    public function cancelWarrant(int $warrantId, int $cancellerId, string $reason): ServiceResult;
}

Policy Framework

Dynamic Authorization Integration

The policy framework provides method-level authorization granularity through custom policy classes.

PermissionPolicy Entity

File: app/src/Model/Entity/PermissionPolicy.php

/**
 * Permission Policy Entity - Dynamic Authorization Framework
 * 
 * Links permissions to specific policy classes and methods for fine-grained authorization control.
 */
class PermissionPolicy extends BaseEntity
{
    protected array $_accessible = [
        'permission_id' => true,
        'policy_class' => true,    // Full class name (e.g., 'App\Policy\MemberPolicy')
        'policy_method' => true,   // Method name (e.g., 'canEdit')
    ];
}

Policy Discovery and Registration

// From PermissionsLoader getApplicationPolicies()
public static function getApplicationPolicies(): array
{
    $paths = [];
    
    // Application policies: app/src/Policy/
    $appPolicyPath = realpath(__DIR__ . '/../Policy');
    if ($appPolicyPath !== false) {
        $paths[] = $appPolicyPath;
    }
    
    // Plugin policies: app/plugins/*/src/Policy/
    $pluginPolicyDirs = glob(__DIR__ . '/../../plugins/*/src/Policy', GLOB_ONLYDIR);
    foreach ($pluginPolicyDirs as $dir) {
        $realDir = realpath($dir);
        if ($realDir !== false) {
            $paths[] = $realDir;
        }
    }
    
    // Discover and analyze policy classes
    $policyClasses = [];
    foreach (get_declared_classes() as $class) {
        $reflector = new ReflectionClass($class);
        $methods = [];
        
        foreach ($reflector->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            // Only include methods starting with 'can'
            if (preg_match('/^can/', $method->getName())) {
                $methods[] = $method->getName();
            }
        }
        
        if (!empty($methods)) {
            $policyClasses[$class] = $methods;
        }
    }
    
    return $policyClasses;
}

Policy Integration Example

// Permission with policy association
$permission = [
    'name' => 'Edit Member Profiles',
    'scoping_rule' => Permission::SCOPE_BRANCH_ONLY,
    'policies' => [
        'App\Policy\MemberPolicy' => [
            'canEdit' => $policyId
        ]
    ]
];

// Policy method implementation
class MemberPolicy extends BasePolicy
{
    public function canEdit(Member $user, Member $target): bool
    {
        // Custom authorization logic
        if ($user->hasRole('Admin')) {
            return true;
        }
        
        if ($user->id === $target->id) {
            return true; // Users can edit their own profiles
        }
        
        return false;
    }
}

Implementation Examples

Basic Permission Checking

// Controller authorization example
public function edit($id)
{
    $member = $this->Members->get($id);
    
    // Check permission through authorization service
    $this->Authorization->authorize($member, 'edit');
    
    // Continue with edit logic
}

Advanced Permission Loading

// Load complete permission set for member
$permissions = PermissionsLoader::getPermissions($memberId);

// Check specific permission with branch scope
if (isset($permissions[$permissionId])) {
    $permission = $permissions[$permissionId];
    
    if ($permission->scoping_rule === Permission::SCOPE_GLOBAL || 
        in_array($currentBranchId, $permission->branch_ids)) {
        // Permission granted
        return true;
    }
}

return false;

Policy Framework Usage

// Get policy mappings for member
$policies = PermissionsLoader::getPolicies($memberId, [$branchId]);

// Check specific policy method
if (isset($policies['App\Policy\MemberPolicy']['canEdit'])) {
    $policy = $policies['App\Policy\MemberPolicy']['canEdit'];
    
    // Use with CakePHP Authorization plugin
    $result = $this->Authorization->can($entity, 'edit');
}

Warrant-Secured Operations

// Service method requiring warrant validation
public function performSensitiveOperation($memberId, $branchId)
{
    // Load permissions with warrant validation
    $permissions = PermissionsLoader::getPermissions($memberId);
    
    // Check for warrant-secured permission
    foreach ($permissions as $permission) {
        if ($permission->name === 'Sensitive Operation' && 
            $permission->requires_warrant && 
            in_array($branchId, $permission->branch_ids ?? [])) {
            
            // Permission granted with warrant validation
            return $this->executeSensitiveOperation();
        }
    }
    
    throw new UnauthorizedException('Warrant-secured permission required');
}

Security Considerations

Attack Prevention

1. Privilege Escalation Prevention

2. Data Protection

3. Configuration Security

// Secure configuration examples
'KMP' => [
    'RequireActiveWarrantForSecurity' => 'yes',  // Enable warrant validation
    'MinPasswordComplexity' => 8,               // Password requirements
    'SessionTimeout' => 3600,                   // Session security
    'FailedLoginAttempts' => 5,                 // Brute force protection
]

Input Validation

Permission Creation Validation

// From PermissionsTable validation rules
$validator
    ->requirePresence('name', 'create')
    ->notEmptyString('name')
    ->maxLength('name', 255)
    ->add('scoping_rule', 'inList', [
        'rule' => ['inList', [
            Permission::SCOPE_GLOBAL,
            Permission::SCOPE_BRANCH_ONLY,
            Permission::SCOPE_BRANCH_AND_CHILDREN
        ]]
    ])
    ->boolean('require_active_membership')
    ->boolean('require_active_background_check')
    ->naturalNumber('require_min_age')
    ->boolean('requires_warrant');

Age Validation Logic

// Complex age validation in PermissionsLoader
->where([
    'OR' => [
        'Permissions.require_min_age' => 0,
        'AND' => [
            // Edge case: Birthday this year but hasn't occurred yet
            'Members.birth_year = ' . strval($now->year) . ' - Permissions.require_min_age',
            'Members.birth_month <=' => $now->month,
        ],
        // Standard case: Birth year is before the required age cutoff
        'Members.birth_year < ' . strval($now->year) . ' - Permissions.require_min_age',
    ],
])

Performance Optimization

Multi-Tier Caching Strategy

1. Permission Caching

// From PermissionsLoader getPermissions()
$cacheKey = 'member_permissions' . $memberId;
$cache = Cache::read($cacheKey, 'member_permissions');
if ($cache) {
    return $cache; // Return cached result if available
}

// ... complex permission loading logic ...

Cache::write($cacheKey, $permissions, 'member_permissions');

2. Policy Caching

// Policy mappings cache
$cacheKey = 'permissions_policies' . $id;
$cache = Cache::read($cacheKey, 'member_permissions');

3. Branch Hierarchy Caching

// From BranchesTable
$cacheKey = 'branch_descendants_' . $id;
$cached = Cache::read($cacheKey, static::CACHE_CONFIG);
if ($cached !== null) {
    return $cached;
}

Query Optimization

1. Efficient JOIN Strategy

// From PermissionsLoader validPermissionClauses()
$q = $q->innerJoinWith('Roles.Members') // Efficient role-member association
    ->select([
        'Permissions.id',
        'Permissions.name', 
        'Permissions.scoping_rule',
        'MemberRoles.branch_id',
    ])
    ->distinct(); // Prevent duplicate permissions

2. Subquery Optimization

// Warrant validation subquery
$warrantSubquery = $warrantsTable->find()
    ->select(['Warrants.member_role_id']) // Only need role linkage
    ->where([
        'Warrants.start_on <' => $now,
        'Warrants.expires_on >' => $now,
        'Warrants.status' => Warrant::CURRENT_STATUS,
    ]);

3. Cache Invalidation Strategy

Three-Tier Cache Invalidation (from BaseTable):

// Static application-level caches
Cache::clear('branches');
Cache::clear('members');

// Entity-specific caches  
Cache::delete('member_permissions' . $memberId, 'member_permissions');

// Group-based cache clearing
Cache::clearGroup('permissions', 'member_permissions');

Troubleshooting

Common Authorization Issues

1. Permission Not Found

Symptoms: User reports access denied for valid operation Diagnosis:

// Debug permission loading
$permissions = PermissionsLoader::getPermissions($memberId);
debug($permissions);

// Check specific permission
if (!isset($permissions[$expectedPermissionId])) {
    // Permission not assigned to user
}

Solutions:

2. Branch Scope Issues

Symptoms: Permission denied in specific branches Diagnosis:

$permission = $permissions[$permissionId];
echo "Scoping rule: " . $permission->scoping_rule . "\n";
echo "Allowed branches: " . print_r($permission->branch_ids, true) . "\n";
echo "Current branch: " . $currentBranchId . "\n";

Solutions:

3. Warrant Validation Failures

Symptoms: Permission denied when warrant should be valid Diagnosis:

// Check warrant configuration
$useWarrant = StaticHelpers::getAppSetting('KMP.RequireActiveWarrantForSecurity');
echo "Warrant validation enabled: " . $useWarrant . "\n";

// Check warrant status
$warrant = $warrantsTable->find()
    ->where(['member_role_id' => $memberRoleId])
    ->first();
debug($warrant);

Solutions:

Symptoms: Stale permissions after role changes Solutions:

// Clear specific member permissions
Cache::delete('member_permissions' . $memberId, 'member_permissions');

// Clear all permission caches
Cache::clearGroup('permissions', 'member_permissions');

// Clear branch hierarchy caches
Cache::clear('branches');

Performance Debugging

1. Slow Permission Loading

Diagnosis:

$start = microtime(true);
$permissions = PermissionsLoader::getPermissions($memberId);
$end = microtime(true);
echo "Permission loading took: " . ($end - $start) . " seconds\n";

Optimization:

2. Memory Usage

Diagnosis:

echo "Memory usage: " . memory_get_usage(true) / 1024 / 1024 . " MB\n";
echo "Peak memory: " . memory_get_peak_usage(true) / 1024 / 1024 . " MB\n";

Optimization:

Error Logging

// Enable detailed authorization logging
Log::debug('Permission check failed', [
    'member_id' => $memberId,
    'permission' => $permissionName,
    'branch_id' => $branchId,
    'reason' => $failureReason
]);

Configuration Reference

Required App Settings

// config/app.php
'KMP' => [
    // Warrant validation
    'RequireActiveWarrantForSecurity' => 'yes',
    
    // Security settings
    'MinPasswordComplexity' => 8,
    'SessionTimeout' => 3600,
    'FailedLoginAttempts' => 5,
    
    // Cache settings
    'CachePermissions' => true,
    'PermissionCacheTimeout' => 3600,
]

Cache Configuration

// config/app.php Cache section
'member_permissions' => [
    'className' => 'File',
    'path' => CACHE . 'member_permissions' . DS,
    'duration' => '+1 hours',
    'groups' => ['permissions']
],
'permissions' => [
    'className' => 'File', 
    'path' => CACHE . 'permissions' . DS,
    'duration' => '+1 day',
    'groups' => ['permissions']
]

This comprehensive RBAC security architecture provides the foundation for secure, scalable, and maintainable authorization within the KMP system, ensuring proper access control across the complex SCA organizational hierarchy while maintaining performance and security standards.


This document is based on the actual implementation documented in the KMP codebase as of July 2025.