Initial commit
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Security\Authorization;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Bank;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Transaction;
|
||||
use App\Models\TransactionType;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Namu\WireChat\Models\Conversation;
|
||||
use Namu\WireChat\Models\Message;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Export Profile Data Authorization Tests
|
||||
*
|
||||
* Tests that users can only export their own profile data and cannot export
|
||||
* data from profiles they don't own/manage.
|
||||
*
|
||||
* @group security
|
||||
* @group authorization
|
||||
* @group export
|
||||
* @group critical
|
||||
*/
|
||||
class ExportProfileDataAuthorizationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Create transaction type for transaction exports
|
||||
\DB::table('transaction_types')->insert([
|
||||
'id' => 1,
|
||||
'name' => 'worked_hours',
|
||||
'label' => 'Worked Hours',
|
||||
'icon' => 'clock',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can export their own transactions
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_can_export_own_transactions()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$recipient = User::factory()->create();
|
||||
|
||||
$userAccount = Account::factory()->create([
|
||||
'accountable_type' => User::class,
|
||||
'accountable_id' => $user->id,
|
||||
]);
|
||||
|
||||
$recipientAccount = Account::factory()->create([
|
||||
'accountable_type' => User::class,
|
||||
'accountable_id' => $recipient->id,
|
||||
]);
|
||||
|
||||
Transaction::factory()->create([
|
||||
'from_account_id' => $userAccount->id,
|
||||
'to_account_id' => $recipientAccount->id,
|
||||
'amount' => 60,
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTransactions', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user cannot export another user's transactions via session manipulation
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_cannot_export_another_users_transactions()
|
||||
{
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
|
||||
$user2Account = Account::factory()->create([
|
||||
'accountable_type' => User::class,
|
||||
'accountable_id' => $user2->id,
|
||||
]);
|
||||
|
||||
// Logged in as user1
|
||||
$this->actingAs($user1, 'web');
|
||||
|
||||
// Malicious: manipulate session to target user2
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTransactions', 'json');
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization can export own transactions
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_can_export_own_transactions()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$organization->users()->attach($user->id);
|
||||
|
||||
$orgAccount = Account::factory()->create([
|
||||
'accountable_type' => Organization::class,
|
||||
'accountable_id' => $organization->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($organization, 'organization');
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $organization->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTransactions', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization cannot export another organization's transactions
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_cannot_export_another_organizations_transactions()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$org1 = Organization::factory()->create();
|
||||
$org2 = Organization::factory()->create();
|
||||
$org1->users()->attach($user->id);
|
||||
|
||||
// Logged in as both web user and organization
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($org1, 'organization');
|
||||
|
||||
// Malicious: manipulate session to target org2
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $org2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTransactions', 'json');
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can export own profile data
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_can_export_own_profile_data()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportProfileData', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user cannot export another user's profile data
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_cannot_export_another_users_profile_data()
|
||||
{
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
|
||||
// Logged in as user1
|
||||
$this->actingAs($user1, 'web');
|
||||
|
||||
// Malicious: manipulate session to target user2
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportProfileData', 'json');
|
||||
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can export own messages
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_can_export_own_messages()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$recipient = User::factory()->create();
|
||||
|
||||
// Create a conversation using sendMessageTo
|
||||
$user->sendMessageTo($recipient, 'Test message');
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportMessages', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user cannot export another user's messages
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_cannot_export_another_users_messages()
|
||||
{
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
$recipient = User::factory()->create();
|
||||
|
||||
// Create messages for user2
|
||||
$user2->sendMessageTo($recipient, 'User2 private message');
|
||||
|
||||
// Logged in as user1
|
||||
$this->actingAs($user1, 'web');
|
||||
|
||||
// Malicious: manipulate session to target user2
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportMessages', 'json');
|
||||
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can export own tags
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_can_export_own_tags()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
// Create a tag and attach to user
|
||||
$tag = Tag::factory()->create();
|
||||
$user->tags()->attach($tag->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTags', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user cannot export another user's tags
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_cannot_export_another_users_tags()
|
||||
{
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
|
||||
// Create tags for user2
|
||||
$tag = Tag::factory()->create();
|
||||
$user2->tags()->attach($tag->id);
|
||||
|
||||
// Logged in as user1
|
||||
$this->actingAs($user1, 'web');
|
||||
|
||||
// Malicious: manipulate session to target user2
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTags', 'json');
|
||||
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can export own contacts
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_can_export_own_contacts()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$contact = User::factory()->create();
|
||||
|
||||
// Create a transaction to establish contact
|
||||
$userAccount = Account::factory()->create([
|
||||
'accountable_type' => User::class,
|
||||
'accountable_id' => $user->id,
|
||||
]);
|
||||
|
||||
$contactAccount = Account::factory()->create([
|
||||
'accountable_type' => User::class,
|
||||
'accountable_id' => $contact->id,
|
||||
]);
|
||||
|
||||
Transaction::factory()->create([
|
||||
'from_account_id' => $userAccount->id,
|
||||
'to_account_id' => $contactAccount->id,
|
||||
'amount' => 60,
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportContacts', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user cannot export another user's contacts
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function user_cannot_export_another_users_contacts()
|
||||
{
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
|
||||
// Logged in as user1
|
||||
$this->actingAs($user1, 'web');
|
||||
|
||||
// Malicious: manipulate session to target user2
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportContacts', 'json');
|
||||
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization can export own messages
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_can_export_own_messages()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$organization->users()->attach($user->id);
|
||||
$recipient = User::factory()->create();
|
||||
|
||||
// Create a conversation from organization
|
||||
$organization->sendMessageTo($recipient, 'Org message');
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($organization, 'organization');
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $organization->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportMessages', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization cannot export another organization's messages
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_cannot_export_another_organizations_messages()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$org1 = Organization::factory()->create();
|
||||
$org2 = Organization::factory()->create();
|
||||
$org1->users()->attach($user->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($org1, 'organization');
|
||||
|
||||
// Malicious: manipulate session to target org2
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $org2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportMessages', 'json');
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization can export own tags
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_can_export_own_tags()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$organization->users()->attach($user->id);
|
||||
|
||||
// Create a tag and attach to organization
|
||||
$tag = Tag::factory()->create();
|
||||
$organization->tags()->attach($tag->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($organization, 'organization');
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $organization->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTags', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization cannot export another organization's tags
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_cannot_export_another_organizations_tags()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$org1 = Organization::factory()->create();
|
||||
$org2 = Organization::factory()->create();
|
||||
$org1->users()->attach($user->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($org1, 'organization');
|
||||
|
||||
// Malicious: manipulate session to target org2
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $org2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTags', 'json');
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization can export own contacts
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_can_export_own_contacts()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$organization->users()->attach($user->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($organization, 'organization');
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $organization->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportContacts', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test organization cannot export another organization's contacts
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function organization_cannot_export_another_organizations_contacts()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$org1 = Organization::factory()->create();
|
||||
$org2 = Organization::factory()->create();
|
||||
$org1->users()->attach($user->id);
|
||||
|
||||
$this->actingAs($user, 'web');
|
||||
$this->actingAs($org1, 'organization');
|
||||
|
||||
// Malicious: manipulate session to target org2
|
||||
session(['activeProfileType' => Organization::class, 'activeProfileId' => $org2->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportContacts', 'json');
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cross-guard attack: web user cannot export bank data
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function web_user_cannot_export_bank_data_cross_guard_attack()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bank = Bank::factory()->create();
|
||||
$bank->managers()->attach($user->id);
|
||||
|
||||
// Logged in as user (web guard)
|
||||
$this->actingAs($user, 'web');
|
||||
|
||||
// Malicious: manipulate session to target bank profile
|
||||
session(['activeProfileType' => Bank::class, 'activeProfileId' => $bank->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportProfileData', 'json');
|
||||
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bank can export own data when properly authenticated
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function bank_can_export_own_data_when_properly_authenticated()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$bank = Bank::factory()->create();
|
||||
$bank->managers()->attach($user->id);
|
||||
|
||||
// Properly logged in as bank
|
||||
$this->actingAs($bank, 'bank');
|
||||
session(['activeProfileType' => Bank::class, 'activeProfileId' => $bank->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportProfileData', 'json');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unauthenticated user cannot export any data
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function unauthenticated_user_cannot_export_data()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
// Not authenticated
|
||||
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id]);
|
||||
|
||||
$response = Livewire::test(\App\Http\Livewire\Profile\ExportProfileData::class)
|
||||
->call('exportTransactions', 'json');
|
||||
|
||||
// Should return 401 (not authenticated) rather than 403 (authenticated but unauthorized)
|
||||
$response->assertStatus(401);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user