Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View 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`