266 lines
8.6 KiB
PHP
266 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Security\IDOR;
|
|
|
|
use App\Models\User;
|
|
use App\Models\Organization;
|
|
use App\Models\Bank;
|
|
use App\Models\Admin;
|
|
use App\Models\Post;
|
|
use App\Models\Transaction;
|
|
use App\Models\Account;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Profile Access IDOR (Insecure Direct Object Reference) Tests
|
|
*
|
|
* Tests that users cannot access resources belonging to other users
|
|
* by manipulating IDs in requests.
|
|
*
|
|
* @group security
|
|
* @group idor
|
|
* @group critical
|
|
*/
|
|
class ProfileAccessIDORTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* Test user cannot view another user's private profile data via ID manipulation
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_view_another_users_private_profile_via_id()
|
|
{
|
|
// Arrange: Create two users
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
|
|
// Act: Login as user1, try to access user2's profile edit page
|
|
$response = $this->actingAs($user1)
|
|
->get(route('profile.edit'));
|
|
|
|
// Try to load user2's data by manipulating session
|
|
session(['activeProfileId' => $user2->id]);
|
|
|
|
$response2 = $this->actingAs($user1)
|
|
->get(route('profile.edit'));
|
|
|
|
// Assert: Should not be able to see user2's private data
|
|
// This is a basic check - deeper validation would require inspecting response content
|
|
$this->assertTrue(true); // Placeholder for more specific assertions
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access another user's account balance via ID manipulation
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_another_users_account_balance()
|
|
{
|
|
// Arrange: Create two users with accounts
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
|
|
$account1 = Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => $user1->id,
|
|
]);
|
|
|
|
$account2 = Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => $user2->id,
|
|
]);
|
|
$account3 = Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => User::factory()->create()->id,
|
|
]);
|
|
|
|
// A transaction of user2 that does not involve user1
|
|
Transaction::factory()->create([
|
|
'from_account_id' => $account2->id,
|
|
'to_account_id' => $account3->id,
|
|
'amount' => 100,
|
|
'description' => 'Private to user2',
|
|
]);
|
|
|
|
// Act: Login as user1 and point the account list at user2's account
|
|
$this->actingAs($user1, 'web');
|
|
session(['activeProfileType' => User::class, 'activeProfileId' => $user1->id]);
|
|
|
|
// Assert: the account must belong to the active profile, so nothing of user2 is listed
|
|
Livewire::test(\App\Http\Livewire\TransactionsTable::class)
|
|
->set('fromAccountId', $account2->id)
|
|
->assertDontSee('Private to user2');
|
|
}
|
|
|
|
/**
|
|
* Test user cannot view another user's transaction history via ID manipulation
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_view_another_users_transaction_history()
|
|
{
|
|
// Arrange: a transaction between user2 and user3, not involving user1
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
$user3 = User::factory()->create();
|
|
|
|
Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => $user1->id,
|
|
]);
|
|
$account2 = Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => $user2->id,
|
|
]);
|
|
$account3 = Account::factory()->create([
|
|
'accountable_type' => User::class,
|
|
'accountable_id' => $user3->id,
|
|
]);
|
|
|
|
$privateTransaction = Transaction::factory()->create([
|
|
'from_account_id' => $account2->id,
|
|
'to_account_id' => $account3->id,
|
|
'amount' => 500,
|
|
'description' => 'Private transaction between user2 and user3',
|
|
]);
|
|
|
|
$this->actingAs($user1, 'web');
|
|
session(['activeProfileType' => User::class, 'activeProfileId' => $user1->id]);
|
|
|
|
// The own history is available
|
|
$this->get(route('transactions'))->assertSuccessful();
|
|
|
|
// Opening the other transaction's statement by id is refused
|
|
$response = $this->get(route('transaction.show', ['transactionId' => $privateTransaction->id]));
|
|
$response->assertForbidden();
|
|
$response->assertDontSee('Private transaction between user2 and user3');
|
|
}
|
|
|
|
/**
|
|
* Test user cannot edit another user's post via ID manipulation
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_edit_another_users_post()
|
|
{
|
|
// Posts are edited in the post manager, which is for admins and the central bank only;
|
|
// a regular user cannot open it, so no post of another profile can be edited
|
|
$user1 = User::factory()->create();
|
|
|
|
$this->actingAs($user1, 'web');
|
|
session(['activeProfileType' => User::class, 'activeProfileId' => $user1->id]);
|
|
|
|
$this->get(route('posts.manage'))->assertForbidden();
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access organization dashboard they're not linked to
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_organization_dashboard_without_link()
|
|
{
|
|
// Arrange: Create user and two organizations
|
|
$user = User::factory()->create();
|
|
$org1 = Organization::factory()->create();
|
|
$org2 = Organization::factory()->create();
|
|
|
|
// Link user to org1 only
|
|
$user->organizations()->attach($org1->id);
|
|
|
|
// Act: Login as user
|
|
$this->actingAs($user, 'web');
|
|
|
|
// Point the session at org2 (not linked)
|
|
session(['activeProfileType' => 'App\\Models\\Organization']);
|
|
session(['activeProfileId' => $org2->id]); // IDOR attempt
|
|
session(['active_guard' => 'organization']);
|
|
|
|
// Assert: the page is not served and the organization guard stays logged out
|
|
$this->get(route('main'))->assertRedirect();
|
|
$this->assertGuest('organization');
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access bank dashboard they're not linked to
|
|
*
|
|
* @test
|
|
*/
|
|
public function user_cannot_access_bank_dashboard_without_link()
|
|
{
|
|
// Arrange: Create user and two banks
|
|
$user = User::factory()->create();
|
|
$bank1 = Bank::factory()->create();
|
|
$bank2 = Bank::factory()->create();
|
|
|
|
// Link user to bank1 only
|
|
$user->banksManaged()->attach($bank1->id);
|
|
|
|
// Act: Login as user
|
|
$this->actingAs($user, 'web');
|
|
|
|
// Point the session at bank2 (not linked)
|
|
session(['activeProfileType' => 'App\\Models\\Bank']);
|
|
session(['activeProfileId' => $bank2->id]); // IDOR attempt
|
|
session(['active_guard' => 'bank']);
|
|
|
|
// Assert: the page is not served and the bank guard stays logged out
|
|
$this->get(route('main'))->assertRedirect();
|
|
$this->assertGuest('bank');
|
|
}
|
|
|
|
/**
|
|
* Test user cannot access admin panel by ID manipulation
|
|
*
|
|
* @test
|
|
*/
|
|
public function regular_user_cannot_access_admin_panel()
|
|
{
|
|
// Arrange: Create regular user and admin
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
|
|
// Act: Login as regular user
|
|
$this->actingAs($user);
|
|
|
|
// Try to manipulate session to become admin
|
|
session(['activeProfileType' => 'App\\Models\\Admin']);
|
|
session(['activeProfileId' => $admin->id]); // IDOR attempt
|
|
session(['active_guard' => 'admin']);
|
|
|
|
// Try to access admin dashboard
|
|
$response = $this->get('/admin/dashboard');
|
|
|
|
// Assert: Should be denied
|
|
$this->assertTrue(
|
|
$response->status() === 403 || $response->status() === 302 || $response->status() === 404,
|
|
"Regular user should not access admin panel"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test API endpoints validate ownership (if applicable)
|
|
*
|
|
* @test
|
|
*/
|
|
public function api_endpoints_validate_resource_ownership()
|
|
{
|
|
// Arrange: Create two users
|
|
$user1 = User::factory()->create();
|
|
$user2 = User::factory()->create();
|
|
|
|
// Act: Login as user1
|
|
$this->actingAs($user1);
|
|
|
|
// Try to access user2's data via API (if API exists)
|
|
// This is a placeholder test - actual implementation depends on API structure
|
|
|
|
// Assert: Proper 403 or ownership validation
|
|
$this->assertTrue(true); // Placeholder
|
|
}
|
|
}
|