5.6.8 AuthorizationApproval Entity Reference
Last Updated: December 3, 2025
Status: Complete
Scope: Activities Plugin - AuthorizationApproval Entity
Comprehensive technical reference for the AuthorizationApproval entity, covering data model, approval workflow architecture, security implementation, and integration patterns with the multi-level authorization approval system.
Table of Contents
- AuthorizationApproval Entity Overview
- Database Schema
- Entity Properties
- Approval Workflow Architecture
- Security Architecture
- Relationships
- Mass Assignment Security
- Usage Examples
- Integration Points
AuthorizationApproval Entity Overview
The AuthorizationApproval entity represents an individual approver’s response within the authorization approval workflow. Each AuthorizationApproval tracks a single approver’s decision (approve/deny) for a specific authorization request, including timing, notes, and secure token validation. This entity enables multi-level approval workflows where multiple approvers may be required.
Core Responsibilities:
- Track individual approver decisions within multi-level approval workflows
- Maintain secure token-based approval validation for email workflows
- Record timing and accountability for all approval decisions
- Provide complete approval history and traceability
- Enable approval analytics and performance metrics
Location: plugins/Activities/src/Model/Entity/AuthorizationApproval.php
Extends: App\Model\Entity\BaseEntity
Table Class: Activities\Model\Table\AuthorizationApprovalsTable
Database Schema
Table: activities_authorization_approvals
The AuthorizationApproval entity maps to the activities_authorization_approvals database table with the following structure:
| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
id |
INT(11) | NO | Auto | Primary key, auto-incrementing |
authorization_id |
INT(11) | NO | Foreign key to Authorization entity | |
approver_id |
INT(11) | NO | Foreign key to Member entity (approver) | |
authorization_token |
VARCHAR(255) | NO | Secure token for email-based approval validation | |
requested_on |
DATE | NO | When approval was requested from this approver | |
responded_on |
DATE | YES | NULL | When approver provided their response |
approved |
TINYINT(1) | NO | Approver’s decision (true = approved, false = denied) | |
approver_notes |
VARCHAR(255) | YES | ’’ | Optional notes from approver explaining decision |
created |
TIMESTAMP | NO | CURRENT | Creation timestamp (inherited from BaseEntity) |
created_by |
INT(11) | YES | NULL | User who created record (inherited from BaseEntity) |
modified |
TIMESTAMP | NO | CURRENT | Last modification timestamp (inherited from BaseEntity) |
modified_by |
INT(11) | YES | NULL | User who last modified record (inherited from BaseEntity) |
Indexes:
- PRIMARY:
id - INDEX:
authorization_id(for approval status checks) - INDEX:
approver_id(for member’s pending approval queue) - INDEX:
responded_on(for pending approval queries) - UNIQUE:
authorization_token(for secure email validation)
Foreign Keys:
authorization_id→activities_authorizations.id(ON DELETE CASCADE)approver_id→members.id(ON DELETE CASCADE)
Notes:
- Inherits audit fields from
BaseEntity:created_by,modified_by - Secure token enables email-based approval workflows
- Responds to cascading deletes from related entities for data integrity
Entity Properties
Core Identification Properties
id
- Type:
int - Required: Yes (auto-generated)
- Description: Unique approval record identifier
- Use Case: Primary key for database operations
authorization_id
- Type:
int - Required: Yes
- Constraint: Must reference existing Authorization entity
- Description: The Authorization entity being evaluated
- Relationship:
belongsTo Authorization - Cascade: Record deleted if Authorization deleted
- Example:
123
approver_id
- Type:
int - Required: Yes
- Constraint: Must reference existing Member entity
- Description: Member acting as approver for this request
- Relationship:
belongsTo Member(as approver) - Cascade: Record deleted if Member deleted
- Example:
456
Approval Workflow Properties
authorization_token
- Type:
string(VARCHAR 255) - Required: Yes
- Constraint: Must be unique across approval records
- Description: Secure token for email-based approval validation
- Format: Cryptographically secure random string (e.g., 32 characters)
- Security: Prevents unauthorized approval manipulation
- Use Case: Email approval links contain token for direct approval/denial
- Validation: Required on entity creation
- Immutability: Should not change after creation
- Example:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
requested_on
- Type:
\Cake\I18n\Date - Required: Yes
- Description: When the approval was requested from this approver
- Set By: Controller on approval record creation
- Usage: Establishes baseline for response time tracking
- Required On: Entity creation phase
- Workflow Phase: Request Creation
- Example:
2024-03-15
responded_on
- Type:
\Cake\I18n\Date|null - Required: No
- Default: NULL (until approver responds)
- Description: When the approver provided their response
- Null Meaning: Approval request is still pending
- Set When: Approver submits approval or denial decision
- Workflow Phase: Response Resolution
- Query Pattern: WHERE responded_on IS NULL for pending approvals
- Example:
2024-03-17or NULL
approved
- Type:
bool(stored as TINYINT(1)) - Required: Yes
- Values:
true(1): Approver approved the authorizationfalse(0): Approver denied the authorization
- Description: The approver’s final decision
- Set When: Approval decision is recorded
- Query Pattern: WHERE approved = true for approved records
- Example:
trueorfalse
Notes and Communication Properties
approver_notes
-
Type: string(VARCHAR 255)null - Required: No
- Default: Empty string or NULL
- Max Length: 255 characters
- Description: Optional notes from approver explaining their decision
- Examples:
- “Qualifications verified and approved”
- “Does not meet minimum age requirement”
- “Awaiting additional documentation”
- Use Case: Decision justification and approver communication
- Nullable: Can be empty for decisions without explanation
Audit Properties
The following properties are inherited from BaseEntity:
created
- Type:
\Cake\I18n\DateTime(TIMESTAMP) - Required: Yes
- Set By: Automatic on insert
- Description: When the approval record was created
- Immutable: Should not be modified after creation
created_by
- Type:
int|null - Set By: Automatic via BaseEntity behavior
- Description: User ID who created the approval record
modified
- Type:
\Cake\I18n\DateTime(TIMESTAMP) - Set By: Automatic on updates
- Description: Last modification timestamp
modified_by
- Type:
int|null - Set By: Automatic via BaseEntity behavior
- Description: User ID who last modified the record
Approval Workflow Architecture
Request → Review → Decision → Completion Cycle
1. Request Phase
Trigger: AuthorizationApprovalsTable creates record for each required approver
1. AuthorizationManager identifies required approvers
2. For each approver, create AuthorizationApproval entity:
- authorization_id: linking to Authorization
- approver_id: identified approver
- authorization_token: unique secure token generated
- requested_on: current date
- responded_on: NULL (pending response)
3. Save approval record to database
4. Send email notification with approval link (includes token)
State After Request Phase:
requested_on: Set to current dateresponded_on: NULL (pending)approved: Not yet decidedapprover_notes: Not yet provided
Database Entry Example:
INSERT INTO activities_authorization_approvals (
authorization_id,
approver_id,
authorization_token,
requested_on,
approved,
created,
created_by
) VALUES (
123, -- Authorization ID
456, -- Approver Member ID
'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', -- Secure token
CURDATE(), -- Today's date
NULL, -- No decision yet (actually boolean, set to NULL initially)
NOW(), -- Current timestamp
789 -- User creating record
);
2. Notification Phase
Email system sends notification with secure approval link:
Email URL Example:
https://kmp.example.org/activities/authorization-approvals/respond?
token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&
decision=approve
Query Pattern:
SELECT * FROM activities_authorization_approvals
WHERE authorization_token = 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
AND responded_on IS NULL
3. Review Phase
Approver evaluates authorization request:
Approver's Actions:
1. Receive email with secure approval link
2. Review authorization details via email or web link
3. Evaluate against activity requirements
4. Make approval or denial decision
5. Optionally add notes explaining decision
4. Decision Phase
Approver submits decision:
When Approver Clicks "Approve" or "Deny":
1. Controller retrieves approval by token
2. Validate token matches pending approval
3. Verify approver_id matches authenticated user
4. Update approval record:
- responded_on = current date
- approved = true or false
- approver_notes = optional text provided
5. Save updated record
6. Trigger AuthorizationManager to process decision
Update Example:
$approval->responded_on = FrozenDate::now();
$approval->approved = true; // or false for denial
$approval->approver_notes = "Qualifications verified";
$authorizationApprovalsTable->save($approval);
5. Completion Phase
Authorization workflow processes approval outcome:
For Each Approval Response:
1. Check if all required approvals received
2. Count approvals vs. denials vs. pending
3. Transition Authorization status:
- If unanimous approval → APPROVED status
- If any denial → DENIED status
- If pending expires → EXPIRED status
4. Assign role if APPROVED
5. Send notification emails to requestor
6. Update dashboard and navigation badges
Multi-Level Approval Support
The table architecture supports complex approval workflows:
Sequential Approvals
Authorization Request
↓
Create AuthorizationApproval #1 for Approver A
↓
Approver A reviews and responds
↓
Create AuthorizationApproval #2 for Approver B (only after A approves)
↓
Approver B reviews and responds
↓
Check all responses and determine final status
Parallel Approvals
Authorization Request
↓
Create AuthorizationApproval #1 for Approver A
Create AuthorizationApproval #2 for Approver B
Create AuthorizationApproval #3 for Approver C
↓
All three approvers review simultaneously
↓
Collect responses as they arrive
↓
When all responded, check:
- Unanimous approval → APPROVED
- Any denial → DENIED
Security Architecture
Token-Based Security
Secure Token System
Token Generation:
$authorization_token = Security::randomString(32);
// Generates cryptographically secure 32-character token
Token Characteristics:
- Cryptographically secure random generation
- 32 characters long (alphanumeric)
- Unique across all approval records
- Single-use (validated for pending approvals only)
- Tied to specific authorization_id
Security Benefits:
- Prevents replay attacks (token specific to authorization)
- Prevents unauthorized approval manipulation
- Enables secure email-based approval workflows
- No sensitive data transmitted in URLs
Email Approval Workflow
1. Authorization requires approval
2. Generate unique token for each approver
3. Create email-safe approval URLs:
- Approve: /activities/authorization-approvals/respond?token=XXX&decision=approve
- Deny: /activities/authorization-approvals/respond?token=XXX&decision=deny
4. Send emails with secure links
5. Approver clicks link and responds
6. Validate token, verify user, record decision
Access Control Integration
Approver Validation:
- Ensure only authorized members can approve requests
- Validate approver_id matches authenticated user
- Check member has approval permission for activity
- Integrate with RBAC system for authority validation
Audit Trail:
- Complete accountability through audit fields
- created_by and modified_by track user actions
- Timestamps record exact decision timing
- All changes logged for compliance
Relationships
Parent Relationships (belongsTo)
authorization
- Related Entity:
Activities\Model\Entity\Authorization - Foreign Key:
authorization_id - Join Type: INNER JOIN (required)
- Description: The Authorization entity being evaluated
- Access:
$approval->authorization->member->sca_name - Cascade Delete: If Authorization deleted, approval deleted
- Contains Pattern:
'contain' => [ 'Authorizations' => [ 'Activities', 'Members' ] ]
member (as approver)
- Related Entity:
App\Model\Entity\Member - Foreign Key:
approver_id - Join Type: INNER JOIN (required)
- Description: Member responsible for approval decision
- Access:
$approval->member->sca_name(or through approver alias) - Cascade Delete: If Member deleted, approval deleted
- Use Case: Approver contact info and profile
Query Patterns with Relationships
Get approval with full context:
$approval = $approvalsTable->get($id, [
'contain' => [
'Authorizations' => [
'Activities',
'Members' => ['Branches']
]
]
]);
// Access context
echo $approval->authorization->member->sca_name; // Requester
echo $approval->authorization->activity->name; // Activity being approved
echo $approval->member->sca_name; // Approver
echo $approval->authorization->member->branch->name; // Branch
Mass Assignment Security
The AuthorizationApproval entity uses CakePHP’s accessible field system to prevent mass assignment vulnerabilities.
Accessible Fields
The following fields can be safely mass assigned through newEntity() or patchEntity():
protected array $_accessible = [
"authorization_id" => true,
"approver_id" => true,
"authorization_token" => true,
"requested_on" => true,
"responded_on" => true,
"approved" => true,
"approver_notes" => true,
"authorization" => true,
"member" => true,
];
Protected Fields
These fields are NOT accessible via mass assignment:
id- Primary key protectioncreated- Timestamp protection (set by database)created_by- Audit field (set by behavior)modified- Timestamp protection (set by database)modified_by- Audit field (set by behavior)
Safe Entity Creation
// Safe: Only accessible fields can be set
$approval = $approvalsTable->newEntity([
'authorization_id' => $auth->id,
'approver_id' => $approver->id,
'authorization_token' => Security::randomString(32),
'requested_on' => FrozenDate::now()
]);
// Safe: Responding to approval
$approval = $approvalsTable->patchEntity($existing, [
'responded_on' => FrozenDate::now(),
'approved' => true,
'approver_notes' => 'Approved'
]);
Usage Examples
Creating Approval Requests
// Create approval request for single approver
$approval = $authorizationApprovalsTable->newEntity([
'authorization_id' => $authorization->id,
'approver_id' => $approver->id,
'authorization_token' => Security::randomString(32),
'requested_on' => FrozenDate::now(),
'approved' => null,
'approver_notes' => null
]);
if ($authorizationApprovalsTable->save($approval)) {
// Send email notification with token
$this->queueMail('Activities', 'approvalRequest',
$approver->email_address,
[
'approval' => $approval,
'authorization' => $authorization,
'token' => $approval->authorization_token
]
);
}
Creating Multi-Level Approvals
// Create approval for each required approver
$approvers = $authorization->activity->getApproversQuery()
->select(['id', 'email_address'])
->all();
$approvalRecords = [];
foreach ($approvers as $approver) {
$approval = $authorizationApprovalsTable->newEntity([
'authorization_id' => $authorization->id,
'approver_id' => $approver->id,
'authorization_token' => Security::randomString(32),
'requested_on' => FrozenDate::now()
]);
if ($authorizationApprovalsTable->save($approval)) {
$approvalRecords[] = $approval;
// Queue email notification
$this->queueMail('Activities', 'approvalRequest',
$approver->email_address,
['approval' => $approval, 'authorization' => $authorization]
);
}
}
Processing Approval Responses
// Find approval by secure token
$approval = $authorizationApprovalsTable->find()
->where(['authorization_token' => $token])
->contain(['Authorizations.Activities', 'Authorizations.Members'])
->first();
if ($approval && empty($approval->responded_on)) {
// Update with approver decision
$approval = $authorizationApprovalsTable->patchEntity($approval, [
'responded_on' => FrozenDate::now(),
'approved' => $decision, // true or false
'approver_notes' => $notes
]);
if ($authorizationApprovalsTable->save($approval)) {
// Process approval outcome
$authorizationManager->processApprovalResponse($approval);
return true;
}
}
Checking Approval Status
// Get all approvals for authorization
$approvals = $authorizationApprovalsTable->find()
->where(['authorization_id' => $authorization->id])
->contain(['Approvers'])
->all();
// Analyze approval status
$totalApprovals = $approvals->count();
$approvedCount = $approvals->countBy(fn($a) => $a->approved === true);
$deniedCount = $approvals->countBy(fn($a) => $a->approved === false);
$pendingCount = $approvals->countBy(fn($a) => empty($a->responded_on));
// Determine authorization status
if ($deniedCount > 0) {
$status = 'DENIED';
} elseif ($approvedCount === $totalApprovals && $pendingCount === 0) {
$status = 'APPROVED';
} else {
$status = 'PENDING';
}
echo sprintf(
"Status: %s (Approved: %d, Denied: %d, Pending: %d)",
$status, $approvedCount, $deniedCount, $pendingCount
);
Finding Pending Approvals
// Get pending approvals for member (as approver)
$pendingApprovals = $authorizationApprovalsTable->find()
->where([
'approver_id' => $member->id,
'responded_on IS' => null
])
->contain([
'Authorizations' => [
'Activities',
'Members'
]
])
->orderBy(['requested_on' => 'ASC'])
->all();
// Display pending approvals
foreach ($pendingApprovals as $approval) {
$hoursWaiting = $approval->requested_on->diffInHours(FrozenDate::now());
echo sprintf(
"%s requests %s approval for %s (pending %d hours)",
$approval->authorization->member->sca_name,
$approval->authorization->activity->name,
$approval->member->sca_name,
$hoursWaiting
);
}
Approval Queue Analytics
// Get member's pending approval count
$pendingCount = AuthorizationApprovalsTable::memberAuthQueueCount($member->id);
// Response time statistics
$stats = $authorizationApprovalsTable->find()
->select([
'count' => $query->func()->count('*'),
'avg_hours' => $query->func()->avg(
$query->func()->timestampdiff(
'HOUR',
'requested_on',
'responded_on'
)
),
'approval_rate' => $query->func()->avg('approved')
])
->where(['responded_on IS NOT' => null])
->first();
// Approval success rate
$successRate = round($stats->approval_rate * 100, 2);
echo sprintf(
"Approval Analytics: %d responses, %.1f avg hours, %.1f%% approval rate",
$stats->count,
$stats->avg_hours,
$successRate
);
Overdue Approval Tracking
// Find overdue approvals (pending > 7 days)
$overdueApprovals = $authorizationApprovalsTable->find()
->where([
'responded_on IS' => null,
'requested_on <' => FrozenDate::now()->subDays(7)
])
->contain([
'Authorizations' => ['Activities', 'Members'],
'Approvers'
])
->orderBy(['requested_on' => 'ASC'])
->all();
// Send reminder notifications
foreach ($overdueApprovals as $approval) {
$daysPending = $approval->requested_on->diffInDays(FrozenDate::now());
$this->queueMail('Activities', 'approvalReminder',
$approval->member->email_address,
[
'approval' => $approval,
'days_pending' => $daysPending,
'authorization' => $approval->authorization
]
);
}
Integration Points
AuthorizationManager Service
The AuthorizationManager service handles approval workflow:
// Request creates approvals
$manager->request($activityId, $memberId, $isRenewal);
// Creates AuthorizationApproval records for each required approver
// Process approval response
$manager->approve($authorizationId, $approverId, $data);
// Updates AuthorizationApproval with decision
// Process denial
$manager->deny($authorizationId, $approverId, $denyReason);
// Updates AuthorizationApproval and sets DENIED status
Email System
Notification delivery for approval workflow:
// Send initial approval request
$this->queueMail('Activities', 'approvalRequest', $email, ['approval' => $approval]);
// Send approval decision notification
$this->queueMail('Activities', 'approvalResponded', $email, ['approval' => $approval]);
// Send reminder for overdue approvals
$this->queueMail('Activities', 'approvalReminder', $email, ['approval' => $approval]);
Navigation System
Pending approval badges and workflow navigation:
// In navigation cell
$pendingCount = AuthorizationApprovalsTable::memberAuthQueueCount($member->id);
if ($pendingCount > 0) {
echo $this->Html->badge($pendingCount, ['class' => 'bg-warning']);
}
MemberAuthorizationsTrait Integration
The MemberAuthorizationsTrait extends Member entities with authorization-related functionality, specifically for tracking pending approval responsibilities.
Trait Location: plugins/Activities/src/Model/Entity/MemberAuthorizationsTrait.php
Usage in Member Entity:
// In Member entity class
use Activities\Model\Entity\MemberAuthorizationsTrait;
class Member extends KMPIdentity {
use MemberAuthorizationsTrait;
}
Key Method - getPendingApprovalsCount():
Returns the count of pending authorization approvals where this member is the designated approver.
// Get pending approvals for navigation badge
$pendingCount = $currentMember->getPendingApprovalsCount();
if ($pendingCount > 0) {
echo "<span class='badge badge-warning'>{$pendingCount}</span>";
}
Query Pattern:
// Filters on approver_id and null responded_on (pending requests)
$approvalsTable->find()
->where([
"approver_id" => $this->id,
"responded_on is" => null,
])
->count();
Integration Points:
- Navigation badges for pending approval indicators
- Dashboard widgets for quick approval status overview
- Approval controllers for workflow entry point validation
- Administrative reports for approval workload analysis
Activity Management
Approver discovery and requirement validation:
// From Activity entity
$approvers = $activity->getApproversQuery($branchId)->all();
// Number of required approvals
$required = $activity->num_required_authorizors;
$renewalRequired = $activity->num_required_renewers;
Member Management
Approver authentication and authorization validation:
// Verify approver identity and permission
$member = $membersTable->get($approverId);
if (!$member->hasPermission('approve_authorizations')) {
throw new ForbiddenException('Member cannot approve authorizations');
}
Performance Considerations
Query Optimization
Indexed Queries:
// Uses index on approver_id + responded_on
$pending = $approvalsTable->find()
->where([
'approver_id' => $memberId,
'responded_on IS' => null
]);
// Uses index on authorization_id
$auths = $approvalsTable->find()
->where(['authorization_id' => $authId]);
// Uses unique index on authorization_token
$approval = $approvalsTable->find()
->where(['authorization_token' => $token])
->first();
Efficient Association Loading:
// Bad: N+1 queries
foreach ($approvals as $approval) {
echo $approval->authorization->member->sca_name;
}
// Good: Single query with contains
$approvals = $approvalsTable->find()
->contain(['Authorizations.Members'])
->all();
Caching Strategy
// Cache pending count for navigation
$cacheKey = "auth_queue_{$memberId}";
$pending = Cache::remember($cacheKey, function () use ($memberId) {
return AuthorizationApprovalsTable::memberAuthQueueCount($memberId);
}, 'short');
References and Related Documentation
Related Entities
- Authorization Entity - Authorization lifecycle and status
- Activity Entity - Activity configuration and approvers
- Member Entity - Member profiles and permissions
Related Documentation
- Activities Plugin Workflows - Authorization lifecycle workflows
- Activities Plugin Architecture - Plugin structure
- Activity Authorization Security Patterns - Security implementation
- RBAC Security Architecture - Permission system
Source Files
- Entity:
plugins/Activities/src/Model/Entity/AuthorizationApproval.php - Table:
plugins/Activities/src/Model/Table/AuthorizationApprovalsTable.php - Base:
App\Model\Entity\BaseEntity- Audit trail functionality
Related Services
Activities\Services\AuthorizationManagerInterface- Business logic for approval workflowApp\Services\PermissionsLoader- Permission-based approver discovery- Email system - Approval notification delivery