diff --git a/config/users.php b/config/users.php index b40f0d3fc6a..9e6fbaafe64 100644 --- a/config/users.php +++ b/config/users.php @@ -176,9 +176,12 @@ | Users may be required to reauthorize before performing certain | sensitive actions. This is called an elevated session. Here | you may configure the duration of the session in minutes. + | You may also disable the elevated session entirely. | */ + 'elevated_sessions_enabled' => env('STATAMIC_ELEVATED_SESSIONS_ENABLED', true), + 'elevated_session_duration' => 15, 'elevated_session_url' => null, diff --git a/resources/js/components/elevated-sessions/index.js b/resources/js/components/elevated-sessions/index.js index fcd8f155f89..47ff4eae486 100644 --- a/resources/js/components/elevated-sessions/index.js +++ b/resources/js/components/elevated-sessions/index.js @@ -1,6 +1,8 @@ import axios from 'axios'; export async function requireElevatedSession() { + if (!Statamic.$config.get('elevatedSessionsEnabled')) return; + const response = await axios.get(cp_url('elevated-session')); if (response.data.elevated) return; diff --git a/routes/cp.php b/routes/cp.php index 2ba14ab8544..6b23b6b9873 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -443,11 +443,13 @@ Route::get('session-timeout', SessionTimeoutController::class)->name('session.timeout'); - Route::get('auth/confirm-password', [ElevatedSessionController::class, 'showForm'])->name('confirm-password'); - Route::get('elevated-session', [ElevatedSessionController::class, 'status'])->name('elevated-session.status'); - Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.cp.passkeys'); - Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.cp.auth'); - Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code'); + if (config('statamic.users.elevated_sessions_enabled')) { + Route::get('auth/confirm-password', [ElevatedSessionController::class, 'showForm'])->name('confirm-password'); + Route::get('elevated-session', [ElevatedSessionController::class, 'status'])->name('elevated-session.status'); + Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.cp.passkeys'); + Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.cp.auth'); + Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code'); + } Route::get('playground', PlaygroundController::class)->name('playground'); diff --git a/routes/web.php b/routes/web.php index 884ff0b2a91..4eab84115c2 100755 --- a/routes/web.php +++ b/routes/web.php @@ -54,12 +54,14 @@ Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset'); Route::post('password/reset', [ResetPasswordController::class, 'reset'])->middleware('throttle:statamic.auth')->name('password.reset.action'); - Route::middleware('auth')->group(function () { - Route::get('confirm-password', [ElevatedSessionController::class, 'showForm'])->name('elevated-session')->middleware([HandleInertiaRequests::class]); - Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.auth'); - Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.passkeys'); - Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code'); - }); + if (config('statamic.users.elevated_sessions_enabled')) { + Route::middleware('auth')->group(function () { + Route::get('confirm-password', [ElevatedSessionController::class, 'showForm'])->name('elevated-session')->middleware([HandleInertiaRequests::class]); + Route::post('elevated-session', [ElevatedSessionController::class, 'confirm'])->name('elevated-session.confirm')->middleware('throttle:statamic.auth'); + Route::get('elevated-session/passkey-options', [ElevatedSessionController::class, 'options'])->name('elevated-session.passkey-options')->middleware('throttle:statamic.passkeys'); + Route::get('elevated-session/resend-code', [ElevatedSessionController::class, 'resendCode'])->name('elevated-session.resend-code')->middleware('throttle:send-elevated-session-code'); + }); + } Route::group(['prefix' => 'passkeys'], function () { Route::middleware('throttle:statamic.passkeys')->group(function () { diff --git a/src/Http/Controllers/CP/CpController.php b/src/Http/Controllers/CP/CpController.php index b7a65ed5f88..322088251d9 100644 --- a/src/Http/Controllers/CP/CpController.php +++ b/src/Http/Controllers/CP/CpController.php @@ -72,7 +72,7 @@ public function authorizeProIf($condition) public function requireElevatedSession(): void { - if (! request()->hasElevatedSession()) { + if (config('statamic.users.elevated_sessions_enabled') && ! request()->hasElevatedSession()) { throw new ElevatedSessionAuthorizationException; } } diff --git a/src/Http/Middleware/RequireElevatedSession.php b/src/Http/Middleware/RequireElevatedSession.php index 773a14255a7..f990cb75dd3 100644 --- a/src/Http/Middleware/RequireElevatedSession.php +++ b/src/Http/Middleware/RequireElevatedSession.php @@ -9,7 +9,7 @@ class RequireElevatedSession { public function handle($request, Closure $next) { - if (! $request->hasElevatedSession()) { + if (config('statamic.users.elevated_sessions_enabled') && ! $request->hasElevatedSession()) { throw new ElevatedSessionAuthorizationException; } diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index eede2567d4f..c2251917c38 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -64,6 +64,7 @@ private function protectedVariables() 'ajaxTimeout' => config('statamic.system.ajax_timeout'), 'googleDocsViewer' => config('statamic.assets.google_docs_viewer'), 'focalPointEditorEnabled' => config('statamic.assets.focal_point_editor'), + 'elevatedSessionsEnabled' => config('statamic.users.elevated_sessions_enabled'), 'user' => $this->user($user), 'defaultPreferences' => Preference::default()->all(), 'paginationSize' => config('statamic.cp.pagination_size'), diff --git a/tests/Auth/ElevatedSessionDisabledTest.php b/tests/Auth/ElevatedSessionDisabledTest.php new file mode 100644 index 00000000000..0ddccfe06ee --- /dev/null +++ b/tests/Auth/ElevatedSessionDisabledTest.php @@ -0,0 +1,55 @@ +user = User::make()->email('foo@bar.com')->makeSuper()->password('secret'); + $this->user->save(); + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app['config']->set('statamic.users.elevated_sessions_enabled', false); + } + + #[Test] + public function cp_elevated_session_routes_are_not_registered() + { + $this->actingAs($this->user); + + $this->get('/cp/elevated-session')->assertNotFound(); + $this->get('/cp/elevated-session/passkey-options')->assertNotFound(); + $this->post('/cp/elevated-session')->assertNotFound(); + $this->get('/cp/elevated-session/resend-code')->assertNotFound(); + $this->get('/cp/auth/confirm-password')->assertNotFound(); + } + + #[Test] + public function frontend_elevated_session_routes_are_not_registered() + { + $this->actingAs($this->user); + + $this->get('/!/auth/confirm-password')->assertNotFound(); + $this->post('/!/auth/elevated-session')->assertNotFound(); + $this->get('/!/auth/elevated-session/passkey-options')->assertNotFound(); + $this->get('/!/auth/elevated-session/resend-code')->assertNotFound(); + } +} diff --git a/tests/Auth/ElevatedSessionTest.php b/tests/Auth/ElevatedSessionTest.php index 523925f00cd..b6bf112d987 100644 --- a/tests/Auth/ElevatedSessionTest.php +++ b/tests/Auth/ElevatedSessionTest.php @@ -321,6 +321,47 @@ public function middleware_denies_request_when_elevated_session_has_expired_via_ ->assertJson(['message' => __('Requires an elevated session.')]); } + #[Test] + public function middleware_does_not_require_elevated_session_when_elevated_session_is_disabled() + { + config(['statamic.users.elevated_sessions_enabled' => false]); + + $this->actingAs($this->user); + + $this + ->get('/requires-elevated-session') + ->assertOk() + ->assertSee('ok'); + } + + #[Test] + public function middleware_does_not_require_elevated_session_when_elevated_session_is_disabled_even_if_session_expired() + { + config(['statamic.users.elevated_sessions_enabled' => false]); + + $this->actingAs($this->user); + + $this + ->withElevatedSession(now()->subMinutes(16)) + ->get('/requires-elevated-session') + ->assertOk() + ->assertSee('ok'); + } + + #[Test] + public function middleware_does_not_require_elevated_session_when_elevated_session_is_disabled_via_json() + { + config(['statamic.users.elevated_sessions_enabled' => false]); + + $this->actingAs($this->user); + + $this + ->withElevatedSession(now()->subMinutes(16)) + ->getJson('/requires-elevated-session') + ->assertOk() + ->assertSee('ok'); + } + #[Test] public function the_session_is_elevated_upon_login() { diff --git a/tests/Feature/Roles/StoreRoleTest.php b/tests/Feature/Roles/StoreRoleTest.php index 999431e2b95..396afd96ebd 100644 --- a/tests/Feature/Roles/StoreRoleTest.php +++ b/tests/Feature/Roles/StoreRoleTest.php @@ -68,6 +68,26 @@ public function it_denies_access_without_active_elevated_session() ->assertRedirect('/cp/auth/confirm-password'); } + #[Test] + public function it_allows_storing_a_role_without_elevated_session_when_elevated_sessions_are_disabled() + { + config(['statamic.users.elevated_sessions_enabled' => false]); + + $this + ->actingAsUserWithPermissions(['edit roles']) + ->store([ + 'title' => 'No Elevated Session', + 'handle' => 'no_elevated_session', + 'permissions' => ['one', 'two'], + ]) + ->assertOk() + ->assertJson(['redirect' => cp_route('roles.index')]); + + $role = Role::find('no_elevated_session'); + $this->assertEquals('No Elevated Session', $role->title()); + $this->assertEquals(['one', 'two'], $role->permissions()->all()); + } + #[Test] public function it_stores_a_role() {