Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions resources/js/components/elevated-sessions/index.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
12 changes: 7 additions & 5 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
14 changes: 8 additions & 6 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Controllers/CP/CpController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Middleware/RequireElevatedSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/Http/View/Composers/JavascriptComposer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
55 changes: 55 additions & 0 deletions tests/Auth/ElevatedSessionDisabledTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Tests\Auth;

use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\User;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

#[Group('elevated-session')]
class ElevatedSessionDisabledTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

private $user;

protected function setUp(): void
{
parent::setUp();

$this->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();
}
}
41 changes: 41 additions & 0 deletions tests/Auth/ElevatedSessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
20 changes: 20 additions & 0 deletions tests/Feature/Roles/StoreRoleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading