mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-19 01:55:08 +00:00
Count files going out unscanned, not log entries about them
The dashboard warned that 62 files had been let through unscanned in the last day. They were 62 activity log entries for 16 files, and none of those files could be downloaded: 50 entries were for files now missing from storage, and 12 for files since deleted. The count read the log, so it counted a file once per attempt and kept counting it after it was deleted, went missing or was scanned clean. It now counts files in their current state: not scanned, let through while the scanner was down or because it could not open them, with that verdict in the last day. The settings screen already counted this way without the time limit; the rule is one File scope used by the dashboard, the settings screen and projectsend:status. The status key keeps its name and meaning, and is now accurate.
This commit is contained in:
@@ -568,12 +568,13 @@ class DashboardController extends Controller
|
||||
'reachable' => $scanner->reachable,
|
||||
'engine' => $scanner->engine,
|
||||
'definitions_age_hours' => $scanner->definitionsAgeHours(),
|
||||
// Files that went out unchecked in the last day. Zero is the
|
||||
// only number that means "protected"; anything else is a
|
||||
// scanner that was down, or files nobody could open.
|
||||
'let_through_24h' => ActivityLog::query()
|
||||
->where('action', Action::FileNotScanned)
|
||||
->where('created_at', '>=', now()->subDay())
|
||||
// Files let through unchecked in the last day that can still
|
||||
// be downloaded. Zero is the only number that means
|
||||
// "protected"; anything else is a scanner that was down, or
|
||||
// files nobody could open. See File::scopeLetThrough().
|
||||
'let_through_24h' => File::query()
|
||||
->letThrough()
|
||||
->where('scanned_at', '>=', now()->subDay())
|
||||
->count(),
|
||||
// Waiting more than an hour: on an installation set to hold,
|
||||
// this is what an outage looks like.
|
||||
|
||||
@@ -383,14 +383,7 @@ class VirusScanningSettingsController extends Controller
|
||||
ScanStatus::UnscannableBlocked->value,
|
||||
])->count(),
|
||||
'never_scanned' => File::query()->neverScanned()->count(),
|
||||
'let_through' => File::query()
|
||||
->where('scan_status', ScanStatus::NotScanned)
|
||||
->whereIn('scan_note', [
|
||||
NotScannedReason::ScannerUnavailable->value,
|
||||
NotScannedReason::TooLarge->value,
|
||||
NotScannedReason::Encrypted->value,
|
||||
])
|
||||
->count(),
|
||||
'let_through' => File::query()->letThrough()->count(),
|
||||
// What a New scan would actually check — see
|
||||
// ScanFileJob::rescannableValues().
|
||||
'scannable' => File::query()
|
||||
|
||||
@@ -319,6 +319,28 @@ class File extends Model
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Files people can download that nothing checked: let through while
|
||||
* the scanner was down, or because it could not open them.
|
||||
*
|
||||
* A state, not a history. The dashboard used to count "let through"
|
||||
* entries in the activity log, which counted a file once per attempt,
|
||||
* and went on counting files that had since been deleted, gone
|
||||
* missing or been scanned clean — none of which is going out
|
||||
* unscanned.
|
||||
*
|
||||
* @param Builder<File> $query
|
||||
*/
|
||||
public function scopeLetThrough(Builder $query): void
|
||||
{
|
||||
$query->where('scan_status', ScanStatus::NotScanned)
|
||||
->whereIn('scan_note', [
|
||||
NotScannedReason::ScannerUnavailable->value,
|
||||
NotScannedReason::TooLarge->value,
|
||||
NotScannedReason::Encrypted->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Files nothing has ever looked at.
|
||||
*
|
||||
|
||||
@@ -445,12 +445,13 @@ class StatusCommand extends Command
|
||||
ScanStatus::Infected->value,
|
||||
ScanStatus::UnscannableBlocked->value,
|
||||
])->count(),
|
||||
// Files that went out unchecked in the last day. Zero is the
|
||||
// only number that means "protected"; anything else is a
|
||||
// scanner that was down, or files nobody could open.
|
||||
'let_through_24h' => ActivityLog::query()
|
||||
->where('action', Action::FileNotScanned)
|
||||
->where('created_at', '>=', now()->subDay())
|
||||
// Files let through unchecked in the last day that can still
|
||||
// be downloaded. Zero is the only number that means
|
||||
// "protected"; anything else is a scanner that was down, or
|
||||
// files nobody could open. See File::scopeLetThrough().
|
||||
'let_through_24h' => File::query()
|
||||
->letThrough()
|
||||
->where('scanned_at', '>=', now()->subDay())
|
||||
->count(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -292,8 +292,10 @@ test('the status command reports an unreachable scanner and what got through', f
|
||||
app(Settings::class)->set(Setting::VirusScannerAddress, 'tcp://nowhere.test:3310');
|
||||
app()->instance(VirusScanner::class, (new FakeVirusScanner)->reports(ScannerStatus::unreachable('no answer')));
|
||||
|
||||
app(App\Modules\Audit\ActivityLogger::class)->logSystem(App\Modules\Audit\Action::FileNotScanned, [
|
||||
'id' => 1, 'name' => 'x', 'reason' => 'scanner_unavailable',
|
||||
File::factory()->create([
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::ScannerUnavailable->value,
|
||||
'scanned_at' => now(),
|
||||
]);
|
||||
|
||||
// statusJson() lives in StatusCommandTest — Pest loads every test
|
||||
@@ -626,3 +628,37 @@ test('a retry scheduled for later is not a scan in progress', function () {
|
||||
->assertJsonPath('running', true);
|
||||
});
|
||||
|
||||
test('"let through" counts files that can still be downloaded unchecked, once each', function () {
|
||||
// What the dashboard reported after a day of real use: 62, for 16
|
||||
// files, none of them downloadable. It counted activity log entries —
|
||||
// one per attempt, and still counting files since deleted, gone
|
||||
// missing or scanned clean.
|
||||
app(Settings::class)->set(Setting::VirusScanningEnabled, true);
|
||||
app(Settings::class)->set(Setting::VirusScannerAddress, 'tcp://scanner.test:3310');
|
||||
app()->instance(VirusScanner::class, new FakeVirusScanner);
|
||||
|
||||
$letThrough = fn (array $overrides = []): File => File::factory()->create(array_merge([
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::ScannerUnavailable->value,
|
||||
'scanned_at' => now(),
|
||||
], $overrides));
|
||||
|
||||
$out = $letThrough();
|
||||
$letThrough()->delete();
|
||||
$letThrough(['scan_status' => ScanStatus::Missing, 'scan_note' => null]);
|
||||
$letThrough(['scan_status' => ScanStatus::Clean, 'scan_note' => null]);
|
||||
$letThrough(['scanned_at' => now()->subDays(2)]);
|
||||
|
||||
foreach (range(1, 3) as $attempt) {
|
||||
app(App\Modules\Audit\ActivityLogger::class)->logSystem(App\Modules\Audit\Action::FileNotScanned, [
|
||||
'id' => $out->id, 'name' => $out->name, 'reason' => 'scanner_unavailable',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
||||
fn (AssertableInertia $page) => $page->where('system.scanning.let_through_24h', 1),
|
||||
);
|
||||
|
||||
expect(scanningStatus()['let_through_24h'])->toBe(1);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user