368 lines
10 KiB
PHP
368 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Security\Authorization;
|
|
|
|
use App\Helpers\ProfileAuthorizationHelper;
|
|
use App\Models\Admin;
|
|
use App\Models\Bank;
|
|
use App\Models\Organization;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* ProfileAuthorizationHelper Multi-Guard Tests
|
|
*
|
|
* Tests that ProfileAuthorizationHelper correctly validates profile access
|
|
* across all authentication guards (web, admin, organization, bank).
|
|
*
|
|
* @group security
|
|
* @group authorization
|
|
* @group multi-guard
|
|
* @group critical
|
|
*/
|
|
class ProfileAuthorizationHelperTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* Test user can access their own user profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_can_access_own_user_profile()
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actingAs($user, 'web');
|
|
|
|
$result = ProfileAuthorizationHelper::can($user);
|
|
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access another user's profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_another_users_profile()
|
|
{
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
$this->actingAs($user1, 'web');
|
|
|
|
$result = ProfileAuthorizationHelper::can($user2);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
/**
|
|
* Test admin can access their own admin profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function admin_can_access_own_admin_profile()
|
|
{
|
|
// Create admin and link to user
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
$admin->users()->attach($user->id);
|
|
|
|
$this->actingAs($admin, 'admin');
|
|
|
|
$result = ProfileAuthorizationHelper::can($admin);
|
|
|
|
$this->assertTrue($result, 'Admin should be able to access their own admin profile');
|
|
}
|
|
|
|
/**
|
|
* Test organization can access their own organization profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function organization_can_access_own_organization_profile()
|
|
{
|
|
// Create organization and link to user
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$organization->users()->attach($user->id);
|
|
|
|
$this->actingAs($organization, 'organization');
|
|
|
|
$result = ProfileAuthorizationHelper::can($organization);
|
|
|
|
$this->assertTrue($result, 'Organization should be able to access their own profile');
|
|
}
|
|
|
|
/**
|
|
* Test bank can access their own bank profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function bank_can_access_own_bank_profile()
|
|
{
|
|
// Create bank and link to user
|
|
$user = User::factory()->create();
|
|
$bank = Bank::factory()->create();
|
|
$bank->managers()->attach($user->id);
|
|
|
|
$this->actingAs($bank, 'bank');
|
|
|
|
$result = ProfileAuthorizationHelper::can($bank);
|
|
|
|
$this->assertTrue($result, 'Bank should be able to access their own bank profile');
|
|
}
|
|
|
|
/**
|
|
* Test user can access organization they are member of (for profile switching)
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_can_access_organization_they_are_member_of()
|
|
{
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$user->organizations()->attach($organization->id);
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
// Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
|
|
$result = ProfileAuthorizationHelper::userOwnsProfile($organization);
|
|
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access organization they are not member of
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_organization_they_are_not_member_of()
|
|
{
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
// User is NOT attached to organization
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
$result = ProfileAuthorizationHelper::can($organization);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
/**
|
|
* Test user can access bank they manage (for profile switching)
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_can_access_bank_they_manage()
|
|
{
|
|
$user = User::factory()->create();
|
|
$bank = Bank::factory()->create();
|
|
$user->banksManaged()->attach($bank->id);
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
// Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
|
|
$result = ProfileAuthorizationHelper::userOwnsProfile($bank);
|
|
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access bank they don't manage
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_bank_they_dont_manage()
|
|
{
|
|
$user = User::factory()->create();
|
|
$bank = Bank::factory()->create();
|
|
// User is NOT attached to bank
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
$result = ProfileAuthorizationHelper::can($bank);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
/**
|
|
* Test user can access admin profile they are linked to (for profile switching)
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_can_access_admin_profile_they_are_linked_to()
|
|
{
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
$user->admins()->attach($admin->id);
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
// Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
|
|
$result = ProfileAuthorizationHelper::userOwnsProfile($admin);
|
|
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access admin profile they are not linked to
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_admin_profile_they_are_not_linked_to()
|
|
{
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
// User is NOT attached to admin
|
|
|
|
$this->actingAs($user, 'web');
|
|
|
|
$result = ProfileAuthorizationHelper::can($admin);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
/**
|
|
* Test admin cannot directly switch to organization (must go through web user)
|
|
*
|
|
* In the application, profile switching flow is: User → Admin → back to User → Organization
|
|
* Direct Admin → Organization switching is not supported.
|
|
*
|
|
* @test
|
|
*/
|
|
public function admin_can_access_organization_via_linked_user()
|
|
{
|
|
// Create user linked to both admin and organization
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
|
|
$admin->users()->attach($user->id);
|
|
$user->organizations()->attach($organization->id);
|
|
|
|
$this->actingAs($admin, 'admin');
|
|
|
|
// userOwnsProfile() only checks web guard, so this should return false
|
|
// Profile switching requires being on web guard first
|
|
$result = ProfileAuthorizationHelper::userOwnsProfile($organization);
|
|
|
|
$this->assertFalse($result, 'Admin cannot directly switch to organization without going through web user first');
|
|
}
|
|
|
|
/**
|
|
* Test organization cannot directly switch to bank (must go through web user)
|
|
*
|
|
* In the application, profile switching flow is: User → Organization → back to User → Bank
|
|
* Direct Organization → Bank switching is not supported.
|
|
*
|
|
* @test
|
|
*/
|
|
public function organization_can_access_bank_via_linked_user()
|
|
{
|
|
// Create user linked to both organization and bank
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$bank = Bank::factory()->create();
|
|
|
|
$organization->users()->attach($user->id);
|
|
$user->banksManaged()->attach($bank->id);
|
|
|
|
$this->actingAs($organization, 'organization');
|
|
|
|
// userOwnsProfile() only checks web guard, so this should return false
|
|
// Profile switching requires being on web guard first
|
|
$result = ProfileAuthorizationHelper::userOwnsProfile($bank);
|
|
|
|
$this->assertFalse($result, 'Organization cannot directly switch to bank without going through web user first');
|
|
}
|
|
|
|
/**
|
|
* Test admin cannot access unrelated organization
|
|
*
|
|
* @test
|
|
*/
|
|
public function admin_cannot_access_unrelated_organization()
|
|
{
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
|
|
$admin->users()->attach($user->id);
|
|
// User is NOT linked to organization
|
|
|
|
$this->actingAs($admin, 'admin');
|
|
|
|
$result = ProfileAuthorizationHelper::can($organization);
|
|
|
|
$this->assertFalse($result, 'Admin should NOT access unrelated organization');
|
|
}
|
|
|
|
/**
|
|
* Test authorize method throws 403 for unauthorized access
|
|
*
|
|
* @test
|
|
*/
|
|
public function authorize_method_throws_403_for_unauthorized_access()
|
|
{
|
|
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
|
$this->expectExceptionMessage('Unauthorized');
|
|
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
$this->actingAs($user1, 'web');
|
|
|
|
ProfileAuthorizationHelper::authorize($user2);
|
|
}
|
|
|
|
/**
|
|
* Test unauthenticated access is denied
|
|
*
|
|
* @test
|
|
*/
|
|
public function unauthenticated_access_is_denied()
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$result = ProfileAuthorizationHelper::can($user);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
/**
|
|
* Test authorize method throws 401 for unauthenticated access
|
|
*
|
|
* @test
|
|
*/
|
|
public function authorize_method_throws_401_for_unauthenticated()
|
|
{
|
|
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
|
$this->expectExceptionMessage('Authentication required');
|
|
|
|
$user = User::factory()->create();
|
|
|
|
ProfileAuthorizationHelper::authorize($user);
|
|
}
|
|
|
|
/**
|
|
* Test admin cannot access another admin profile
|
|
*
|
|
* @test
|
|
*/
|
|
public function admin_cannot_access_another_admin_profile()
|
|
{
|
|
$user1 = User::factory()->create();
|
|
$admin1 = Admin::factory()->create();
|
|
$admin2 = Admin::factory()->create();
|
|
|
|
$admin1->users()->attach($user1->id);
|
|
|
|
$this->actingAs($admin1, 'admin');
|
|
|
|
$result = ProfileAuthorizationHelper::can($admin2);
|
|
|
|
$this->assertFalse($result, 'Admin should NOT access another admin profile');
|
|
}
|
|
}
|