mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
1e30e83f11
The command writes Setting::CaptchaProvider = 'none'. On an installation using the platform's managed keys, Captcha::resolve() returns managedConfig() — read from config — before it ever looks at that setting, so the write lands somewhere nothing reads and every form stays protected. The command then printed "CAPTCHA is off". That is false in the worst direction: the person running this is locked out and debugging, and the message sends them away from the one thing that would have explained why they are still being challenged. It now says it changed nothing, and names PROJECTSEND_CAPTCHA_DISABLED, which is checked ahead of the key source and is therefore the only one of the two escape hatches that works on a managed installation. The docblock said those two were equivalent; they never were. Deliberately not gated behind captcha.configure. Gating it would take a self-hosted operator's way back in — the alternative being a hand-edited database row — to close something that on a managed installation does nothing anyway. Reaching it needs a shell in the container, which needs an RCE, at which point the CAPTCHA is not the problem. The command had no test at all. It has three now, including one that pins the ordering inside resolve(): if the environment check ever moves below the key source, a locked-out operator loses their last way in.
87 lines
3.5 KiB
PHP
87 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Modules\Platform\Capabilities\Edition;
|
|
use App\Modules\Platform\Captcha\Captcha;
|
|
use App\Modules\Platform\Captcha\CaptchaProvider;
|
|
use App\Modules\Platform\Captcha\CaptchaSettings;
|
|
use App\Modules\Platform\Settings\Setting;
|
|
use App\Modules\Platform\Settings\Settings;
|
|
|
|
beforeEach(function () {
|
|
// Settings outlive the per-test rollback, so nothing here may assume a
|
|
// default — see the same note in CaptchaSettingsTest.
|
|
$settings = app(Settings::class);
|
|
$settings->set(Setting::CaptchaProvider, 'turnstile');
|
|
$settings->set(Setting::CaptchaKeySource, 'own');
|
|
|
|
CaptchaSettings::for(CaptchaProvider::Turnstile)
|
|
->fill(['site_key' => 'site-abc', 'secret_key' => 'secret-abc'])
|
|
->save();
|
|
|
|
config()->set('projectsend.edition', Edition::Community);
|
|
config()->set('projectsend.captcha.disabled', false);
|
|
config()->set('projectsend.captcha.managed', ['provider' => null, 'site_key' => null, 'secret_key' => null, 'score_threshold' => 0.5]);
|
|
|
|
Captcha::forgetDisplayCache();
|
|
});
|
|
|
|
test('it switches the captcha off and keeps the keys', function () {
|
|
expect(app(Captcha::class)->active())->not->toBeNull();
|
|
|
|
$this->artisan('projectsend:captcha-off')
|
|
->expectsOutputToContain('CAPTCHA is off')
|
|
->assertSuccessful();
|
|
|
|
expect(app(Captcha::class)->active())->toBeNull()
|
|
// The whole point of the command: a way back in, not a way to lose
|
|
// a credential somebody wants again in ten minutes.
|
|
->and(CaptchaSettings::for(CaptchaProvider::Turnstile)->secret_key)->toBe('secret-abc');
|
|
});
|
|
|
|
// The setting this command writes is not where managed keys come from.
|
|
// Captcha::resolve() returns managedConfig() before it ever reads
|
|
// Setting::CaptchaProvider, so on a managed installation the write lands
|
|
// somewhere nothing reads — and the old success message sent an operator
|
|
// who was still being challenged away from the only thing that would have
|
|
// explained why.
|
|
test('it says plainly that it changed nothing when the platform supplies the keys', function () {
|
|
config()->set('projectsend.edition', Edition::Cloud);
|
|
config()->set('projectsend.captcha.managed', [
|
|
'provider' => 'turnstile',
|
|
'site_key' => 'managed-site',
|
|
'secret_key' => 'managed-secret',
|
|
'score_threshold' => 0.5,
|
|
]);
|
|
app(Settings::class)->set(Setting::CaptchaKeySource, 'managed');
|
|
Captcha::forgetDisplayCache();
|
|
|
|
$this->artisan('projectsend:captcha-off')
|
|
->expectsOutputToContain('Nothing changed')
|
|
->expectsOutputToContain('PROJECTSEND_CAPTCHA_DISABLED')
|
|
->doesntExpectOutputToContain('CAPTCHA is off')
|
|
->assertSuccessful();
|
|
|
|
// And it really did change nothing: the forms are still protected.
|
|
expect(app(Captcha::class)->active())->not->toBeNull();
|
|
});
|
|
|
|
// The env switch is checked ahead of the key source, which is what makes
|
|
// it the one that works on a managed installation. If that ordering ever
|
|
// moves, a locked-out operator loses their last way in.
|
|
test('the environment switch turns off even the platform keys', function () {
|
|
config()->set('projectsend.edition', Edition::Cloud);
|
|
config()->set('projectsend.captcha.managed', [
|
|
'provider' => 'turnstile',
|
|
'site_key' => 'managed-site',
|
|
'secret_key' => 'managed-secret',
|
|
'score_threshold' => 0.5,
|
|
]);
|
|
app(Settings::class)->set(Setting::CaptchaKeySource, 'managed');
|
|
config()->set('projectsend.captcha.disabled', true);
|
|
Captcha::forgetDisplayCache();
|
|
|
|
expect(app(Captcha::class)->active())->toBeNull();
|
|
});
|