Sindbad~EG File Manager
# System-Wide Access Control Implementation Guide
## Overview
This document outlines the comprehensive access control system implemented across all modules to restrict data visibility based on user roles (Assembly Admin, District Admin, Area Admin, and Superuser).
## Access Level Hierarchy
```
Superuser (Top Level - See Everything)
↓
Area Admin (See all districts and assemblies in their area)
↓
District Admin (See all assemblies in their district)
↓
Assembly Admin (See only their specific assembly)
```
---
## Helper Functions Added to `config/config.php`
### 1. **Core Access Level Functions**
```php
// Check user roles
isSuperuser() // Returns true if user is superuser
isAreaAdmin() // Returns true if user is area admin or higher
isDistrictAdmin() // Returns true if user is district admin or higher
isAssemblyAdmin() // Returns true if user is assembly admin or higher
// Get user identifiers
getUserAccessLevel() // Returns: 'superuser', 'area', 'district', 'assembly'
getUserAreaId() // Returns user's area_id
getUserDistrictId() // Returns user's district_id
getUserAssemblyId() // Returns user's assembly_id
```
### 2. **SQL Filter Functions**
#### **applyAccessLevelFilter($tableAlias, $options)**
Returns both WHERE clause and parameters for SQL queries.
**Parameters:**
- `$tableAlias`: Table alias (e.g., 'm' for members)
- `$options`: Array with keys:
- `area_column`: Column name for area_id (default: 'area_id')
- `district_column`: Column name for district_id (default: 'district_id')
- `assembly_column`: Column name for assembly_id (default: 'assembly_id')
- `bypass_superuser`: If true, superusers also get filtered (default: false)
**Returns:** `['where' => string, 'params' => array]`
**Example:**
```php
$filter = applyAccessLevelFilter('m');
$query = "SELECT * FROM members m WHERE 1=1" . $filter['where'];
$stmt = $db->prepare($query);
$stmt->execute($filter['params']);
```
#### **getAccessLevelWhere($tableAlias, $options)**
Quick helper that returns just the WHERE clause string.
**Example:**
```php
$query = "SELECT * FROM members m WHERE 1=1" . getAccessLevelWhere('m');
```
#### **getAccessLevelParams($tableAlias, $options)**
Quick helper that returns just the params array.
**Example:**
```php
$stmt->execute(getAccessLevelParams('m'));
```
### 3. **Record Access Validation**
#### **canAccessRecord($recordAreaId, $recordDistrictId, $recordAssemblyId)**
Checks if current user can view a specific record based on its location.
**Example:**
```php
if (!canAccessRecord($member['area_id'], $member['district_id'], $member['assembly_id'])) {
die('Access denied');
}
```
### 4. **UI Helper Functions**
#### **getUserAccessScope()**
Returns human-readable description of user's access scope.
**Returns:**
- "All Areas" (superuser)
- "Madina District" (district admin)
- "Central Assembly" (assembly admin)
#### **getUserAccessBadge()**
Returns HTML badge for displaying user's access level.
**Example Output:**
```html
<span class="px-3 py-1 rounded-full bg-yellow-100 text-yellow-800">
<i class="fas fa-church mr-1"></i>Central Assembly
</span>
```
---
## Important: Events Table Structure
⚠️ **The `events` table uses a different structure:**
- Uses `location_type` (enum: 'area', 'district', 'assembly')
- Uses `location_id` (stores the ID of the area/district/assembly)
- Uses `start_date` and `end_date` (not `event_date`)
This means events require **custom filtering logic** instead of the standard helper functions.
**Example for Events:**
```php
$eventQuery = "SELECT * FROM events e WHERE e.is_active = 1";
$eventParams = [];
if (!isSuperuser()) {
$accessLevel = getUserAccessLevel();
if ($accessLevel === 'assembly') {
$eventQuery .= " AND e.location_type = 'assembly' AND e.location_id = :location_id";
$eventParams['location_id'] = getUserAssemblyId();
} elseif ($accessLevel === 'district') {
$eventQuery .= " AND e.location_type IN ('district', 'assembly')";
// Add logic to check district or assemblies in that district
}
}
```
See `dashboard.php` lines 19-43 for the complete events filtering implementation.
---
## Implementation Examples
### Example 1: Basic Member Query
**Before:**
```php
$query = "SELECT * FROM members WHERE is_active = 1";
$stmt = $db->prepare($query);
$stmt->execute();
```
**After:**
```php
$filter = applyAccessLevelFilter('m');
$query = "SELECT * FROM members m WHERE m.is_active = 1" . $filter['where'];
$stmt = $db->prepare($query);
$stmt->execute($filter['params']);
```
### Example 2: Count Query
**Before:**
```php
$stmt = $db->query("SELECT COUNT(*) as count FROM events");
```
**After:**
```php
$query = "SELECT COUNT(*) as count FROM events e WHERE 1=1" . getAccessLevelWhere('e');
$stmt = $db->prepare($query);
$stmt->execute(getAccessLevelParams('e'));
```
### Example 3: Complex Join Query
```php
$filter = applyAccessLevelFilter('m');
$query = "SELECT m.*, d.district_name, a.assembly_name, ar.area_name
FROM members m
LEFT JOIN districts d ON m.district_id = d.id
LEFT JOIN assemblies a ON m.assembly_id = a.id
LEFT JOIN areas ar ON m.area_id = ar.id
WHERE m.is_active = 1" . $filter['where'] . "
ORDER BY m.first_name";
$stmt = $db->prepare($query);
$stmt->execute($filter['params']);
```
### Example 4: With Additional Filters
```php
$filter = applyAccessLevelFilter('m');
$params = $filter['params'];
$query = "SELECT * FROM members m WHERE m.is_active = 1" . $filter['where'];
// Add search filter
if (!empty($searchTerm)) {
$query .= " AND (m.first_name LIKE :search OR m.last_name LIKE :search)";
$params['search'] = "%$searchTerm%";
}
$stmt = $db->prepare($query);
$stmt->execute($params);
```
### Example 5: Different Column Names
```php
// If table uses different column names
$filter = applyAccessLevelFilter('e', [
'area_column' => 'event_area_id',
'district_column' => 'event_district_id',
'assembly_column' => 'event_assembly_id'
]);
$query = "SELECT * FROM events e WHERE 1=1" . $filter['where'];
```
---
## Files Already Updated
### ✅ Core Files
1. **config/config.php** - All helper functions added
2. **dashboard.php** - Full access control applied
3. **classes/Auth.php** - Session variables set on login
### ✅ Module Pages Already Using Access Control
1. **modules/membership/index.php**
2. **modules/membership/cards.php**
3. **modules/ministries/index.php**
4. **modules/member-codes/index.php**
5. **modules/programs/realtime-attendance.php**
6. **modules/attendance/live-qr.php**
7. **modules/member-accounts/index.php**
---
## Modules That Need Updating
Apply the access control filters to these modules:
### 1. Events Module
- `modules/events/index.php`
- `modules/events/view.php`
- `modules/events/attendance.php`
### 2. Programs Module
- `modules/programs/index.php`
- `modules/programs/view.php`
### 3. Reports Module
- All report pages should filter data by access level
### 4. Communications Module
- `modules/communications/messages.php`
- `modules/communications/sms.php`
### 5. Other Modules
- Any module that displays member-related data
- Any module with area/district/assembly associations
---
## Quick Migration Template
```php
// OLD CODE
$stmt = $db->query("SELECT * FROM table_name");
$results = $stmt->fetchAll();
// NEW CODE WITH ACCESS CONTROL
$filter = applyAccessLevelFilter('t'); // 't' is table alias
$query = "SELECT * FROM table_name t WHERE 1=1" . $filter['where'];
$stmt = $db->prepare($query);
$stmt->execute($filter['params']);
$results = $stmt->fetchAll();
```
---
## Testing Access Control
### Test Accounts to Create
1. **Superuser** - Should see all data
- access_level: 'superuser'
- is_superuser: 1
2. **Area Admin** - Should see all districts/assemblies in area
- access_level: 'area'
- area_id: 1
3. **District Admin** - Should see all assemblies in district
- access_level: 'district'
- district_id: 1
4. **Assembly Admin** - Should see only their assembly
- access_level: 'assembly'
- assembly_id: 1
### Verification Checklist
For each user type, verify:
- [ ] Dashboard shows correct statistics
- [ ] Member list shows only accessible members
- [ ] Events list shows only accessible events
- [ ] Programs list shows only accessible programs
- [ ] Reports generate data for their scope only
- [ ] Cannot access records outside their scope
- [ ] Access badge displays correctly on dashboard
---
## Security Considerations
1. **Always use prepared statements** with the filter parameters
2. **Never bypass access control** unless explicitly required
3. **Validate record access** before showing detailed views
4. **Log access control violations** for security auditing
5. **Test thoroughly** with different user levels
---
## Display Access Badge on Pages
Add this to any page header to show user's access scope:
```php
<div class="mb-6">
<?php echo getUserAccessBadge(); ?>
</div>
```
---
## Common Pitfalls to Avoid
1. **Forgetting to use table aliases**
```php
// WRONG
$filter = applyAccessLevelFilter(); // No alias
// RIGHT
$filter = applyAccessLevelFilter('m'); // With alias
```
2. **Mixing parameters**
```php
// WRONG - Parameter name conflict
$params = ['assembly_id' => 5]; // Conflicts with access_assembly_id
// RIGHT - Use different names
$params = ['filter_assembly_id' => 5];
```
3. **Not using WHERE 1=1**
```php
// WRONG - If access filter is empty, SQL breaks
$query = "SELECT * FROM members m" . $filter['where'];
// RIGHT - WHERE 1=1 ensures valid SQL
$query = "SELECT * FROM members m WHERE 1=1" . $filter['where'];
```
4. **Hardcoding user IDs**
```php
// WRONG
$query .= " AND assembly_id = 1";
// RIGHT - Use helper functions
$query .= getAccessLevelWhere('m');
```
---
## Benefits of This System
1. **Centralized Logic** - All access control in one place
2. **Easy to Apply** - Single function call adds filtering
3. **Consistent Behavior** - All modules follow same rules
4. **Maintainable** - Changes in one place affect everywhere
5. **Secure by Default** - Opt-in for viewing all data
6. **Flexible** - Options for custom column names
7. **Performance** - Database-level filtering (not PHP loops)
---
## Support
For questions or issues:
- Review this document
- Check `config/config.php` for function definitions
- Test with different user access levels
- Verify SQL queries are properly formed
---
**Last Updated:** 2025
**Version:** 1.0
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists