142 lines
9.6 KiB
PHP
142 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Jobs\ScanDocumentForMalware;
|
|
use App\Models\User;
|
|
use App\Services\Malware\MalwareScanner;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
class PortalRealDocumentConsumersTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Storage::fake('local');
|
|
Queue::fake();
|
|
config(['services.malware.scanner' => 'clamd', 'services.malware.host' => '127.0.0.1', 'services.malware.port' => 3310]);
|
|
if (! app(MalwareScanner::class)->health()['available']) {
|
|
$this->markTestSkipped('Real ClamD required for these consumer operations.');
|
|
}
|
|
}
|
|
|
|
private function scan(string $id): void
|
|
{
|
|
(new ScanDocumentForMalware($id))->handle(app(MalwareScanner::class));
|
|
$d = DB::table('documents')->where('id', $id)->first();
|
|
$this->assertSame('clean', $d->malware_scan_status);
|
|
$this->assertSame('ClamAV', $d->malware_engine);
|
|
$this->assertSame(trim($d->sha256), $d->malware_scanned_sha256);
|
|
$this->assertNotNull($d->malware_signatures_at);
|
|
}
|
|
|
|
public function test_library_private_registration_real_scan_explicit_classification_publication_and_download(): void
|
|
{
|
|
$manager = User::factory()->create();
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$bytes = 'Synthetic QA guide; no official or legal value.';
|
|
$upload = ['file' => UploadedFile::fake()->createWithContent('qa-guide.txt', $bytes)];
|
|
$this->actingAs($manager)->post('/management/portal-services/upload', $upload)->assertSessionHasNoErrors();
|
|
$d = DB::table('documents')->first();
|
|
$this->assertSame('quarantined', $d->status);
|
|
$payload = ['document_id' => $d->id, 'title' => 'Synthetic QA guide', 'description' => 'Synthetic quality assurance document.', 'category' => 'Guia', 'topic' => 'Investimento',
|
|
'document_type' => 'guide', 'issuer' => 'Synthetic QA only', 'source_url' => 'https://example.invalid/qa', 'language' => 'pt', 'version_label' => 'QA-1',
|
|
'territorial_scope' => 'national', 'source_confirmation' => 'identified', 'legal_status' => 'not_applicable', 'rights_status' => 'unconfirmed', 'data_classification' => 'internal',
|
|
'source_reference' => 'Synthetic QA fixture, not an official source.', 'legislation' => false, 'essential' => false, 'independent_review' => false, 'associations' => []];
|
|
$response = $this->post('/management/portal-services/resources', $payload);
|
|
$this->assertSame(302, $response->status(), $response->getContent());
|
|
$response->assertSessionHasNoErrors();
|
|
$r = DB::table('portal_resources')->first();
|
|
$this->post('/management/portal-services/resources/'.$r->id, ['published' => true])->assertSessionHasErrors('published');
|
|
$this->get('/documentos/'.$r->id.'/download')->assertNotFound();
|
|
$this->scan($d->id);
|
|
$this->assertDatabaseHas('portal_resources', ['id' => $r->id, 'published' => false]);
|
|
$update = [...$payload, 'metadata_version' => 1, 'rights_status' => 'authorized', 'data_classification' => 'public'];
|
|
$this->put('/management/portal-services/resources/'.$r->id, $update)->assertSessionHasNoErrors();
|
|
$this->get('/documentos/'.$r->id.'/download')->assertNotFound();
|
|
$this->post('/management/portal-services/resources/'.$r->id, ['published' => true])->assertSessionHasNoErrors();
|
|
$this->get('/documentos/'.$r->id.'/download')->assertOk()->assertDownload('qa-guide.txt');
|
|
$this->put('/management/portal-services/resources/'.$r->id, $update)->assertConflict();
|
|
$this->post('/management/portal-services/upload', $upload)->assertSessionHasErrors('file');
|
|
$this->assertSame(1, DB::table('documents')->count());
|
|
$this->assertSame(3, DB::table('portal_resource_revisions')->count());
|
|
Storage::disk('local')->put($d->storage_key, 'Tampered synthetic QA bytes');
|
|
$this->get('/documentos/'.$r->id.'/download')->assertNotFound();
|
|
$this->actingAs(User::factory()->create())->put('/management/portal-services/resources/'.$r->id, [...$update, 'metadata_version' => 2])->assertForbidden();
|
|
}
|
|
|
|
public function test_desk_upload_real_scan_dedup_recovery_foreign_actor_and_tamper(): void
|
|
{
|
|
$owner = User::factory()->create();
|
|
$this->actingAs($owner);
|
|
$id = $this->postJson('/balcao-virtual/dossiers', ['title' => 'Synthetic document QA case', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$base = '/balcao-virtual/dossiers/'.$id;
|
|
$bytes = 'Synthetic confidential QA attachment only.';
|
|
$input = ['version' => 1, 'category' => 'proof', 'source_reference' => 'Synthetic QA owner attachment.', 'consent' => true, 'file' => UploadedFile::fake()->createWithContent('qa-desk.txt', $bytes)];
|
|
$this->post($base.'/documentos', $input, ['Accept' => 'application/json'])->assertOk();
|
|
$link = DB::table('desk_documents')->first();
|
|
$d = DB::table('documents')->where('id', $link->document_id)->first();
|
|
$url = $base.'/documentos/'.$link->id;
|
|
$this->get($url)->assertNotFound();
|
|
$this->scan($d->id);
|
|
$this->get($url)->assertOk()->assertDownload('qa-desk.txt');
|
|
$this->actingAs(User::factory()->create())->get($url)->assertForbidden();
|
|
$this->actingAs($owner)->getJson($base)->assertOk();
|
|
$version = DB::table('desk_cases')->where('id', $id)->value('version');
|
|
$this->post($base.'/documentos', [...$input, 'version' => $version], ['Accept' => 'application/json'])->assertOk();
|
|
$this->assertSame(1, DB::table('desk_documents')->count());
|
|
$this->assertSame(1, DB::table('documents')->count());
|
|
$this->assertDatabaseHas('documents', ['id' => $d->id, 'data_classification' => 'confidential']);
|
|
Storage::disk('local')->put($d->storage_key, 'Tampered QA bytes');
|
|
$this->get($url)->assertNotFound();
|
|
}
|
|
|
|
public function test_encrypted_canonical_original_is_reusable_in_desk_and_manager_library_download(): void
|
|
{
|
|
$owner = User::factory()->create();
|
|
config(['portal.managers' => [$owner->email]]);
|
|
$bytes = "%PDF-1.4\n% Synthetic encrypted canonical QA only.\n%%EOF";
|
|
$doc = (string) Str::uuid();
|
|
$key = 'qa-canonical/'.$doc;
|
|
Storage::disk('local')->put($key, Crypt::encryptString($bytes));
|
|
DB::table('documents')->insert(['id' => $doc, 'source_system' => 'qa', 'file_name' => 'encrypted-qa.pdf', 'media_type' => 'application/pdf', 'storage_key' => $key,
|
|
'sha256' => hash('sha256', $bytes), 'size_bytes' => strlen($bytes), 'storage_encryption' => 'laravel', 'status' => 'quarantined', 'data_classification' => 'confidential', 'uploaded_by' => $owner->id]);
|
|
$this->scan($doc);
|
|
$this->actingAs($owner)->get('/management/documents/'.$doc.'/download')->assertOk()->assertContent($bytes);
|
|
$case = $this->postJson('/balcao-virtual/dossiers', ['title' => 'Synthetic encrypted reuse QA', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$this->postJson('/balcao-virtual/dossiers/'.$case.'/documentos', ['version' => 1, 'document_id' => $doc, 'category' => 'proof', 'source_reference' => 'Existing owned encrypted QA original', 'consent' => true])->assertOk();
|
|
$link = DB::table('desk_documents')->where('document_id', $doc)->first();
|
|
$url = '/balcao-virtual/dossiers/'.$case.'/documentos/'.$link->id;
|
|
$this->get($url)->assertOk()->assertContent($bytes);
|
|
$this->assertSame(1, DB::table('documents')->count());
|
|
$metadata = ['document_id' => $doc, 'title' => 'Synthetic encrypted QA guide', 'description' => 'Synthetic QA only.', 'category' => 'Guia', 'topic' => 'Investimento',
|
|
'document_type' => 'guide', 'source_url' => 'https://example.invalid/qa', 'language' => 'pt', 'version_label' => 'QA-1', 'territorial_scope' => 'national',
|
|
'source_confirmation' => 'identified', 'legal_status' => 'not_applicable', 'rights_status' => 'authorized', 'data_classification' => 'public',
|
|
'source_reference' => 'Synthetic QA, not an official source.', 'legislation' => false, 'essential' => false, 'independent_review' => false, 'associations' => []];
|
|
$this->post('/management/portal-services/resources', $metadata)->assertSessionHasNoErrors();
|
|
$resource = DB::table('portal_resources')->first();
|
|
$public = '/documentos/'.$resource->id;
|
|
$this->get($public.'/download')->assertNotFound();
|
|
$this->get($public.'/view')->assertNotFound();
|
|
$this->post('/management/portal-services/resources/'.$resource->id, ['published' => true])->assertSessionHasNoErrors();
|
|
$this->get($public.'/download')->assertOk()->assertContent($bytes);
|
|
$this->get($public.'/view')->assertOk()->assertContent($bytes)->assertHeader('Content-Type', 'application/pdf')
|
|
->assertHeader('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
|
|
$this->assertSame(1, DB::table('documents')->count());
|
|
Storage::disk('local')->put($key, Crypt::encryptString('Changed synthetic QA bytes'));
|
|
$this->get($url)->assertNotFound();
|
|
$this->get('/management/documents/'.$doc.'/download')->assertNotFound();
|
|
$this->get($public.'/download')->assertNotFound();
|
|
$this->get($public.'/view')->assertNotFound();
|
|
}
|
|
}
|