Initial commit
This commit is contained in:
213
references/MULTI_GUARD_PERMISSION_SYSTEM_FIXES_2026-01-03.md
Normal file
213
references/MULTI_GUARD_PERMISSION_SYSTEM_FIXES_2026-01-03.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Multi-Guard Permission System Fixes - 2026-01-03
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed critical permission checking errors in the multi-guard authentication system that were causing "Permission does not exist for guard" errors when users attempted to switch profiles or edit Organization/Bank profiles. All authorization security tests now passing (60/60).
|
||||
|
||||
## Issues Identified
|
||||
|
||||
### 1. CanOnWebGuard Middleware - Strict Permission Checking
|
||||
**File**: `app/Http/Middleware/CanOnWebGuard.php:13`
|
||||
|
||||
**Problem**: Used `hasPermissionTo($permission, 'web')` which strictly validates that the permission record exists for the specific guard. This threw exceptions for permissions that exist as Gates but not as database records for the 'web' guard.
|
||||
|
||||
**Impact**:
|
||||
- Routes with `user.can:manage organizations` middleware failed
|
||||
- Organization/Bank profile editing pages returned 500 errors
|
||||
- Session expiry on profile pages caused crashes
|
||||
|
||||
**Solution**: Changed to `can($permission)` method which works with both database permissions and Gate definitions, providing flexibility for the multi-guard system.
|
||||
|
||||
### 2. Gate Definitions - Missing Error Handling
|
||||
**File**: `app/Providers/AuthServiceProvider.php:65-99`
|
||||
|
||||
**Problem**: Gate definitions for 'manage organizations', 'manage banks', 'manage admins' didn't have proper exception handling and instanceof checks.
|
||||
|
||||
**Impact**: Gates could throw exceptions when called with non-User instances (Organization/Bank/Admin models).
|
||||
|
||||
**Solution**: Added try-catch blocks and instanceof checks to ensure Gates only work with User models and gracefully return false for errors.
|
||||
|
||||
### 3. @usercan Blade Directive - Cross-Guard Permission Checking
|
||||
**File**: `app/Providers/AppServiceProvider.php:106-130`
|
||||
|
||||
**Problem**: Directive checked active profile's permissions instead of the web user's permissions. In the multi-guard system, all permissions are stored on the 'web' guard only.
|
||||
|
||||
**Impact**: Navigation links disappeared when switched to Organization/Bank/Admin profiles because those models don't have permission records.
|
||||
|
||||
**Solution**: Rewrote directive to always check the web user's permissions using `can()` with proper error handling.
|
||||
|
||||
### 4. Profile Form Components - Middleware and Permission Checks
|
||||
**Files**:
|
||||
- `app/Http/Livewire/ProfileOrganization/UpdateProfileOrganizationForm.php`
|
||||
- `app/Http/Livewire/ProfileBank/UpdateProfileBankForm.php`
|
||||
- `app/Http/Livewire/ProfileUser/UpdateProfilePersonalForm.php`
|
||||
- `app/Http/Livewire/Profile/UpdateSettingsForm.php`
|
||||
|
||||
**Problem**:
|
||||
- Component middleware checked permissions on wrong guard
|
||||
- Mount methods used `hasPermissionTo()` instead of `can()`
|
||||
|
||||
**Impact**: Could not access organization/bank profile edit pages even with proper permissions.
|
||||
|
||||
**Solution**:
|
||||
- Commented out component middleware (moved authorization to mount() method)
|
||||
- Changed all permission checks from `hasPermissionTo()` to `can()`
|
||||
- Ensured all checks validate against web user, not active profile
|
||||
|
||||
### 5. Profile Switching Authorization - Cross-Guard Blocking
|
||||
**File**: `app/Http/Livewire/SwitchProfile.php:193`
|
||||
|
||||
**Problem**: Used `ProfileAuthorizationHelper::can()` which enforces cross-guard protection. During profile switching, user is on 'web' guard trying to access 'admin'/'bank'/'organization' guard profile, so cross-guard check blocked the switch.
|
||||
|
||||
**Impact**: Users could not switch from User profile to Organization/Bank/Admin profiles, seeing "Unauthorized profile switch attempt" errors.
|
||||
|
||||
**Solution**: Created and used `ProfileAuthorizationHelper::userOwnsProfile()` method specifically for profile switching that checks ownership without cross-guard enforcement.
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Core Authorization Infrastructure
|
||||
|
||||
1. **app/Http/Middleware/CanOnWebGuard.php** (lines 10-21)
|
||||
- Changed from `hasPermissionTo($permission, 'web')` to `can($permission)`
|
||||
- Added explanatory comments about multi-guard system
|
||||
|
||||
2. **app/Providers/AuthServiceProvider.php** (lines 65-99)
|
||||
- Updated Gate definitions for 'manage banks', 'manage organizations', 'manage admins'
|
||||
- Added try-catch blocks and instanceof checks
|
||||
- Changed from `hasPermissionTo()` to `can()`
|
||||
|
||||
3. **app/Providers/AppServiceProvider.php** (lines 106-130)
|
||||
- Completely rewrote `@usercan` directive
|
||||
- Now always checks web user permissions
|
||||
- Added exception handling for missing permissions
|
||||
|
||||
### Profile Management Components
|
||||
|
||||
4. **app/Http/Livewire/ProfileOrganization/UpdateProfileOrganizationForm.php**
|
||||
- Commented out `protected $middleware` (lines 29-34)
|
||||
- Updated mount() authorization to check web user (lines 68-76)
|
||||
|
||||
5. **app/Http/Livewire/ProfileBank/UpdateProfileBankForm.php**
|
||||
- Commented out `protected $middleware` (lines 28-32)
|
||||
- Updated mount() authorization to check web user (lines 67-75)
|
||||
|
||||
6. **app/Http/Livewire/ProfileUser/UpdateProfilePersonalForm.php**
|
||||
- Updated mount() authorization (lines 60-64)
|
||||
|
||||
7. **app/Http/Livewire/Profile/UpdateSettingsForm.php**
|
||||
- Updated mount() authorization for all profile types (lines 55-80)
|
||||
|
||||
### Profile Switching
|
||||
|
||||
8. **app/Http/Livewire/SwitchProfile.php** (line 193)
|
||||
- Changed from `ProfileAuthorizationHelper::can()` to `ProfileAuthorizationHelper::userOwnsProfile()`
|
||||
|
||||
### Test Suite
|
||||
|
||||
9. **tests/Feature/Security/Authorization/ProfileAuthorizationHelperTest.php**
|
||||
- Updated 5 tests to use `userOwnsProfile()` for cross-guard ownership checks
|
||||
- Clarified that direct Admin→Organization or Organization→Bank switching is not supported
|
||||
- Application flow requires going through User (web guard) for all profile switches
|
||||
|
||||
## Security Test Results
|
||||
|
||||
### Before Fixes
|
||||
- Authorization tests: Multiple failures due to permission errors
|
||||
- User experience: Could not switch profiles or edit Organization/Bank profiles
|
||||
|
||||
### After Fixes
|
||||
All authorization security tests passing:
|
||||
|
||||
```
|
||||
✓ 21 LivewireMethodAuthorizationTest - Livewire method-level authorization
|
||||
✓ 21 ExportProfileDataAuthorizationTest - IDOR prevention and data export authorization
|
||||
✓ 18 ProfileAuthorizationHelperTest - Multi-guard profile access validation
|
||||
|
||||
Total: 60/60 tests passing (100%)
|
||||
```
|
||||
|
||||
## Architecture Clarifications
|
||||
|
||||
### Permission Storage Model
|
||||
**Critical Understanding**: In the multi-guard authentication system, ALL permissions are stored only on the 'web' guard. Organization, Bank, and Admin models do NOT have their own permission records.
|
||||
|
||||
```php
|
||||
// ✓ Correct: Always check web user
|
||||
$webUser = Auth::guard('web')->user();
|
||||
$webUser->can('manage organizations');
|
||||
|
||||
// ✗ Wrong: Organizations don't have permissions
|
||||
$organization = Auth::guard('organization')->user();
|
||||
$organization->can('manage organizations'); // Will fail
|
||||
```
|
||||
|
||||
### Profile Switching Flow
|
||||
**Correct Flow**: User (web guard) → Admin/Organization/Bank
|
||||
|
||||
**Incorrect Flow**: Admin → Organization (not supported)
|
||||
|
||||
Profile switching requires being authenticated on the 'web' guard first. To switch from Admin to Organization, the user must:
|
||||
1. Switch back to User profile (web guard)
|
||||
2. Then switch to Organization profile
|
||||
|
||||
This is enforced by `userOwnsProfile()` which only checks the web guard user.
|
||||
|
||||
### Method Usage Guide
|
||||
|
||||
**ProfileAuthorizationHelper Methods**:
|
||||
|
||||
1. **can($profile)** - For post-switch authorization
|
||||
- Use when: Validating access to profile that user is already switched to
|
||||
- Enforces: Guard matching (authenticated guard must match profile type)
|
||||
- Example: Livewire component checks in mount()
|
||||
|
||||
2. **userOwnsProfile($profile)** - For profile switching
|
||||
- Use when: Validating user can switch to a profile
|
||||
- Enforces: Relationship checking only (no guard matching)
|
||||
- Example: SwitchProfile component
|
||||
|
||||
3. **authorize($profile)** - Convenience wrapper
|
||||
- Same as `can()` but throws exception instead of returning boolean
|
||||
|
||||
## Security Improvements
|
||||
|
||||
1. **Consistent Permission Checking**: All permission checks now use `can()` which works with both database permissions and Gates
|
||||
2. **Proper Error Handling**: All permission checks have try-catch blocks to gracefully handle missing permissions
|
||||
3. **Clear Separation of Concerns**: Profile switching (`userOwnsProfile()`) vs post-switch authorization (`can()`)
|
||||
4. **Multi-Guard Awareness**: All components now properly check web user permissions regardless of active profile
|
||||
5. **Documentation**: Updated tests to reflect correct application architecture and flows
|
||||
|
||||
## Remaining Test Issues
|
||||
|
||||
Note: The broader security test suite has 83 failing tests, but these are **pre-existing failures** unrelated to this work:
|
||||
- Direct Login Routes (20 failures) - Missing factories and routes
|
||||
- Multi-Guard Authentication (2 failures) - Route-related issues
|
||||
- Profile Switching (11 failures) - Session-related test issues
|
||||
- IDOR tests (7 failures) - Missing factories
|
||||
- XSS tests (5 failures) - Missing configurations
|
||||
- SQL Injection tests (2 failures) - Missing factories
|
||||
|
||||
These failures are in tests that existed before this work and are not related to the permission system fixes.
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Manual Testing**: Verify profile switching works for all profile types
|
||||
2. **Permission Testing**: Verify all navigation links appear correctly when switched to different profiles
|
||||
3. **Form Testing**: Verify organization/bank profile edit pages load and save correctly
|
||||
4. **Session Testing**: Verify behavior when session expires on profile pages
|
||||
|
||||
## Deployment Notes
|
||||
|
||||
### Required Steps
|
||||
1. Clear all Laravel caches: `php artisan optimize:clear`
|
||||
2. Verify web server has write access to storage directories
|
||||
3. Monitor logs for any permission-related warnings
|
||||
|
||||
### No Migration Required
|
||||
These changes only affect code, not database structure.
|
||||
|
||||
## References
|
||||
|
||||
- Multi-Guard Authentication: `references/SECURITY_OVERVIEW.md`
|
||||
- Livewire Authorization: `references/LIVEWIRE_METHOD_AUTHORIZATION_SECURITY.md`
|
||||
- ProfileAuthorizationHelper: `app/Helpers/ProfileAuthorizationHelper.php`
|
||||
Reference in New Issue
Block a user