Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,265 @@
|
||||
<?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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user