diff --git a/app/Services/Billing/InvoiceArchive.php b/app/Services/Billing/InvoiceArchive.php index 90dc632..0018438 100644 --- a/app/Services/Billing/InvoiceArchive.php +++ b/app/Services/Billing/InvoiceArchive.php @@ -148,6 +148,18 @@ final class InvoiceArchive 'driver' => 'local', 'root' => $target->path, 'throw' => false, + // Explicit, because the default follows the process umask and a + // restrictive one leaves the folder 0700. That is not cosmetic: + // whoever COLLECTS from a handover directory is a different account + // — an rsync over ssh, a backup agent — and a directory it cannot + // enter looks to it exactly like an empty one. Reported as "the + // folder arrives empty", with the invoice sitting in it the whole + // time. + 'visibility' => 'public', + 'permissions' => [ + 'file' => ['public' => 0o644, 'private' => 0o600], + 'dir' => ['public' => 0o755, 'private' => 0o700], + ], ]); } } diff --git a/tests/Feature/Billing/InvoiceArchiveTest.php b/tests/Feature/Billing/InvoiceArchiveTest.php index a6facf9..8a7675e 100644 --- a/tests/Feature/Billing/InvoiceArchiveTest.php +++ b/tests/Feature/Billing/InvoiceArchiveTest.php @@ -216,3 +216,26 @@ it('does not prune a destination whose mount has gone away', function () { expect(is_dir($gone))->toBeFalse(); }); + +it('leaves the folder readable by whoever collects from it', function () { + // Not cosmetic. mkdir's mode is masked by the process umask, and a + // restrictive one leaves the folder 0700 — at which point the account that + // collects (an rsync over ssh, a backup agent, anything that is not the + // account that wrote it) cannot enter it, and an unreadable directory looks + // exactly like an empty one. Reported as "the folder arrives empty", with + // the invoice sitting in it the whole time. + $invoice = anInvoice(); + $old = umask(0o077); + + try { + (new ArchiveInvoice($invoice, $this->target))->handle(app(InvoiceArchive::class)); + } finally { + umask($old); + } + + $folder = $this->archive.'/'.now()->format('Y'); + $file = $folder.'/'.$invoice->number.'.pdf'; + + expect(substr(sprintf('%o', fileperms($folder)), -3))->toBe('755') + ->and(substr(sprintf('%o', fileperms($file)), -3))->toBe('644'); +});