mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-18 09:35:07 +00:00
bab90c0ad8
Every upload now starts as "being checked" and is not served to anyone until a scanner has looked at it. Infected files are quarantined: kept on disk, unreachable, waiting for an administrator. The scanner is ClamAV, reached over a socket, streaming the file wherever it is stored — no temporary copy for an S3 or GCS disk. What the scanner answers is a fact; what it means for the file is this installation's setting, so ClamAvScanner knows nothing about settings and ScanPolicy knows nothing about sockets. Three of clamd's own alert options are what make a file it could not open come back as an answer rather than as "OK"; the client maps those to "too large" and "encrypted" instead of to a threat. Both policies default to letting files through, marked "not scanned", which is the product owner's decision: a scanner that cannot answer must not stop people working. Every such file is logged, and the screens that say so come with the rest of this work. Withholding is two rules. A file that is not available drops out of the scopes that answer "what may this person see" — recipients and the public listings, never the uploader's own copy. And every route that puts bytes on the wire asks FileAvailability first: download, thumbnail, preview, share link, the four public routes and both ends of a zip build. A share link minted before the scan finishes says the file is still being checked rather than 404ing. Not yet here, and coming next: the quarantine screen and its permission, the notifications, the settings screen, the hourly retry, the backfill for existing libraries, and the Docker service.
256 lines
11 KiB
PHP
256 lines
11 KiB
PHP
<?php
|
|
|
|
use App\Modules\Platform\Capabilities\Edition;
|
|
use App\Support\EnvFlag;
|
|
|
|
return [
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Edition
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Which edition this installation runs as. Edition is configuration, not a
|
|
| code branch: every behavioural difference between editions must flow
|
|
| through the capability registry, never through ad-hoc edition checks.
|
|
|
|
|
| Supported: "community", "cloud"
|
|
|
|
|
*/
|
|
|
|
'edition' => Edition::from((string) env('PROJECTSEND_EDITION', 'community')),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Uploads
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Where a chunked upload's parts wait while the transfer is running,
|
|
| before they are assembled onto the storage disk. Leave this unset:
|
|
| it exists so the test suite can give each parallel worker its own
|
|
| directory, since parts are real files on a real path rather than a
|
|
| faked disk, and session ids restart at 1 in every worker's database.
|
|
|
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Platform seats
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| How many staff accounts and how many clients this installation may
|
|
| hold. Unset means unlimited, which is every self-hosted install: this
|
|
| exists for a managed one, where the operator sold a number and the
|
|
| application is the only process that can actually count against it.
|
|
|
|
|
| An operator stating the installation's own limit is not the same as
|
|
| the application inventing a plan tier — the distinction config/api.php
|
|
| draws when it declines to key a rate limit off billing. Nothing here
|
|
| knows what a plan is; it accepts a number and refuses to exceed it.
|
|
|
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Capabilities this installation has been told it may not use
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Comma-separated capability keys, subtracted from what the edition
|
|
| grants. Only ever subtracted: nothing here can switch a capability on,
|
|
| because a variable that could would put the hosted edition's screens
|
|
| one line of .env away on every self-hosted install.
|
|
|
|
|
| For a managed installation whose plan does not include something its
|
|
| edition otherwise has -- branding.customize on a free plan is the case
|
|
| this was built for. Unknown keys are ignored rather than fatal: the
|
|
| variable outlives both the plan that wrote it and the release that
|
|
| named the key, and an instance refusing to boot over a stale one would
|
|
| be an outage on upgrade day.
|
|
|
|
|
*/
|
|
|
|
'capabilities_disabled' => env('PROJECTSEND_CAPABILITIES_DISABLED'),
|
|
|
|
'platform' => [
|
|
'max_staff_users' => env('PROJECTSEND_PLATFORM_MAX_STAFF_USERS'),
|
|
'max_clients' => env('PROJECTSEND_PLATFORM_MAX_CLIENTS'),
|
|
|
|
// Seeded into Setting::TwoFactorEnforcement on first boot and never
|
|
// afterwards — see SeedSettingsCommand. Here rather than read from
|
|
// env() at the point of use, because config:cache stops .env being
|
|
// read at all and that is how TRUSTED_PROXIES came to silently do
|
|
// nothing.
|
|
'two_factor_enforcement' => env('PROJECTSEND_TWO_FACTOR_ENFORCEMENT'),
|
|
|
|
// A floor under what a client with no quota of their own gets, in
|
|
// megabytes. Not a setting, for the same reason the seat caps above
|
|
// are not: it is the shape of what the platform sold rather than a
|
|
// preference the installation's administrator is expressing, and an
|
|
// administrator who has chosen a number keeps it — see
|
|
// ClientStorageUsage::defaultQuotaMb().
|
|
//
|
|
// It exists because the setting's own default is 0, and 0 means
|
|
// unlimited. On an installation a platform runs for other people
|
|
// that is one account away from unmetered hosting, and the account
|
|
// does not have to be one the platform created.
|
|
'default_client_quota_mb' => env('PROJECTSEND_PLATFORM_DEFAULT_CLIENT_QUOTA_MB'),
|
|
],
|
|
|
|
'uploads' => [
|
|
'parts_path' => env('UPLOAD_PARTS_PATH'),
|
|
|
|
// How many resumable uploads one account may have in flight.
|
|
//
|
|
// A session holds room on the temporary volume from the moment it
|
|
// is created until it completes, is cancelled, or is swept — and
|
|
// for an account with no storage quota to spend, this number is
|
|
// the only thing bounding how much room that is. The browser
|
|
// uploads a few files at once, so this is far above anything a
|
|
// person does by hand.
|
|
'max_open_sessions' => 25,
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| How downloads leave the server
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Uploads live outside the web root, so PHP authorizes every download
|
|
| before any byte moves. What differs is what happens next: PHP can
|
|
| stream the file itself, or hand the web server a header naming the
|
|
| file and let it do the work. The header is faster and each server
|
|
| spells it differently — a server that does not recognise the one it
|
|
| is sent serves the empty body instead, which is a 0-byte download.
|
|
|
|
|
| 'auto' (the default) uses nginx's X-Accel-Redirect when the server
|
|
| says it is nginx, and PHP streaming otherwise. 'nginx', 'xsendfile'
|
|
| (Apache with mod_xsendfile, or LiteSpeed) and 'php' state it
|
|
| outright. Read here rather than through env() elsewhere, so that
|
|
| `php artisan config:cache` does not silently blank it.
|
|
|
|
|
*/
|
|
|
|
'file_delivery' => env('PROJECTSEND_FILE_DELIVERY', 'auto'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Chunked upload part size (MB)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Each resumable-upload part travels as one request of this size;
|
|
| web-server/PHP body limits only need to cover a single part.
|
|
|
|
|
*/
|
|
|
|
'upload_part_size_mb' => 20,
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| CAPTCHA
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Two things live here rather than in the settings store, for two
|
|
| different reasons.
|
|
|
|
|
| "disabled" is an escape hatch: a wrong secret key cannot lock anybody
|
|
| out (see CaptchaResult), but an operator who has managed it some other
|
|
| way needs a fix that touches no database and needs no working login.
|
|
|
|
|
| The managed keys are the platform's own, applied to every tenant on
|
|
| cloud and absent everywhere else. In config rather than the tenant
|
|
| database so a database dump never carries our credential, and so
|
|
| rotating it is one fleet-wide change instead of a migration. They do
|
|
| nothing without Capability::CaptchaManagedKeys.
|
|
|
|
|
*/
|
|
|
|
'captcha' => [
|
|
// Read strictly rather than cast: `env()` returns anything it does
|
|
// not recognise as the string it was, and every non-empty string
|
|
// is truthy — so `(bool)` would read `PROJECTSEND_CAPTCHA_DISABLED=no`
|
|
// as "yes, disabled" and quietly take the bot protection off the
|
|
// login and registration forms. See App\Support\EnvFlag.
|
|
'disabled' => EnvFlag::isTrue(env('PROJECTSEND_CAPTCHA_DISABLED', false)),
|
|
|
|
'managed' => [
|
|
'provider' => env('PROJECTSEND_CAPTCHA_MANAGED_PROVIDER'),
|
|
'site_key' => env('PROJECTSEND_CAPTCHA_MANAGED_SITE_KEY'),
|
|
'secret_key' => env('PROJECTSEND_CAPTCHA_MANAGED_SECRET_KEY'),
|
|
'score_threshold' => (float) env('PROJECTSEND_CAPTCHA_MANAGED_SCORE_THRESHOLD', 0.5),
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Virus scanning
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Naming an address here makes scanning *managed*: that scanner is used,
|
|
| scanning cannot be switched off from the settings screen, and the
|
|
| connection fields are hidden. It is how a hosted fleet points every
|
|
| site at one scanning service, and a self-hosted operator who would
|
|
| rather configure this in the environment than in the database can use
|
|
| it too. Everything else — what to do with files that cannot be scanned,
|
|
| and what to do while the scanner is down — stays a setting either way,
|
|
| because those are the site's decisions rather than the platform's.
|
|
|
|
|
| Format: unix:///var/run/clamav/clamd.sock, or tcp://host:3310.
|
|
|
|
|
*/
|
|
|
|
'scanning' => [
|
|
'address' => env('PROJECTSEND_SCANNER_ADDRESS'),
|
|
|
|
// How long to wait for the socket, and then for each reply. A scan
|
|
// streams the whole file before the reply comes, so the second one
|
|
// has to allow for the largest file this installation accepts.
|
|
'connect_timeout' => (int) env('PROJECTSEND_SCANNER_CONNECT_TIMEOUT', 5),
|
|
'reply_timeout' => (int) env('PROJECTSEND_SCANNER_REPLY_TIMEOUT', 600),
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Release identity
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Per-release facts that ship with the code. Not settings: they never
|
|
| vary per install or tenant.
|
|
|
|
|
*/
|
|
|
|
// How long a request waits for another one that is already rendering
|
|
// the same thumbnail or preview, before giving up rather than
|
|
// decoding the same image a second time. See ThumbnailGenerator.
|
|
// A slow disk or a large source wants longer than the default 15.
|
|
'rendition_lock_wait_seconds' => env('PROJECTSEND_RENDITION_LOCK_WAIT_SECONDS'),
|
|
|
|
'version' => '2.4.1',
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Official links
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
// Read through App\Modules\Platform\OfficialLinks rather than
|
|
// directly: which of the two front doors "website" means, and whether
|
|
// the donation link is offered at all, both depend on the edition.
|
|
'links' => [
|
|
'website' => 'https://www.projectsend.org/',
|
|
// The hosted service's own front door. A managed installation
|
|
// links here instead — including from the "Powered by" line on
|
|
// client-facing pages and outgoing email.
|
|
'website_cloud' => 'https://www.projectsend.cloud/',
|
|
// Where this code lives, and the same repository
|
|
// CheckForUpdatesCommand asks for the latest release. v1 remains
|
|
// available at github.com/projectsend/legacy.
|
|
'source' => 'https://github.com/projectsend/projectsend',
|
|
'open_collective' => 'https://opencollective.com/projectsend',
|
|
// Kept identical to the invitation update.sh prints when an update
|
|
// finishes — the two are the same offer, made in the terminal and
|
|
// then again on the screen the administrator lands on.
|
|
'discord' => 'https://discord.gg/VT9n6cyvXT',
|
|
],
|
|
|
|
];
|