mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Add sudo mode service
This commit is contained in:
parent
8ddedb038e
commit
fecb7ba4d8
@ -43,6 +43,8 @@ SilverStripe\Core\Injector\Injector:
|
||||
Authenticators:
|
||||
cms: '%$SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator'
|
||||
SilverStripe\Security\IdentityStore: '%$SilverStripe\Security\AuthenticationHandler'
|
||||
SilverStripe\Security\SudoMode\SudoModeServiceInterface:
|
||||
class: SilverStripe\Security\SudoMode\SudoModeService
|
||||
|
||||
SilverStripe\Security\PasswordExpirationMiddleware:
|
||||
default_redirect: Security/changepassword
|
||||
|
@ -6,8 +6,11 @@ use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Security\AuthenticationHandler;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\SudoMode\SudoModeServiceInterface;
|
||||
|
||||
/**
|
||||
* Authenticate a member passed on a session cookie
|
||||
@ -72,13 +75,20 @@ class SessionAuthenticationHandler implements AuthenticationHandler
|
||||
{
|
||||
static::regenerateSessionId();
|
||||
$request = $request ?: Controller::curr()->getRequest();
|
||||
$request->getSession()->set($this->getSessionVariable(), $member->ID);
|
||||
$session = $request->getSession();
|
||||
$session->set($this->getSessionVariable(), $member->ID);
|
||||
|
||||
// This lets apache rules detect whether the user has logged in
|
||||
// @todo make this a setting on the authentication handler
|
||||
if (Member::config()->get('login_marker_cookie')) {
|
||||
Cookie::set(Member::config()->get('login_marker_cookie'), 1, 0);
|
||||
}
|
||||
|
||||
// Activate sudo mode on login so the user doesn't have to reauthenticate for sudo
|
||||
// actions until the sudo mode timeout expires
|
||||
/** @var SudoModeServiceInterface $service */
|
||||
$service = Injector::inst()->get(SudoModeServiceInterface::class);
|
||||
$service->activate($session);
|
||||
}
|
||||
|
||||
/**
|
||||
|
52
src/Security/SudoMode/SudoModeService.php
Normal file
52
src/Security/SudoMode/SudoModeService.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security\SudoMode;
|
||||
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
class SudoModeService implements SudoModeServiceInterface
|
||||
{
|
||||
use Configurable;
|
||||
|
||||
/**
|
||||
* The lifetime that sudo mode authorization lasts for, in minutes.
|
||||
*
|
||||
* Note that if the PHP session times out before this lifetime is reached, it will automatically be reset.
|
||||
* @see \SilverStripe\Control\Session::$timeout
|
||||
*/
|
||||
private static int $lifetime_minutes = 45;
|
||||
|
||||
/**
|
||||
* The session key that is used to store the timestamp for when sudo mode was last activated
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const SUDO_MODE_SESSION_KEY = 'sudo-mode-last-activated';
|
||||
|
||||
public function check(Session $session): bool
|
||||
{
|
||||
$lastActivated = $session->get(self::SUDO_MODE_SESSION_KEY);
|
||||
// Not activated at all
|
||||
if (!$lastActivated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Activated within the last "lifetime" window
|
||||
$nowTimestamp = DBDatetime::now()->getTimestamp();
|
||||
|
||||
return $lastActivated > ($nowTimestamp - $this->getLifetime() * 60);
|
||||
}
|
||||
|
||||
public function activate(Session $session): bool
|
||||
{
|
||||
$session->set(self::SUDO_MODE_SESSION_KEY, DBDatetime::now()->getTimestamp());
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLifetime(): int
|
||||
{
|
||||
return (int) static::config()->get('lifetime_minutes');
|
||||
}
|
||||
}
|
31
src/Security/SudoMode/SudoModeServiceInterface.php
Normal file
31
src/Security/SudoMode/SudoModeServiceInterface.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security\SudoMode;
|
||||
|
||||
use SilverStripe\Control\Session;
|
||||
|
||||
/**
|
||||
* A service class responsible for activating and checking the current status of elevated permission levels
|
||||
* via "sudo mode". This is done by checking a timestamp value in the provided session.
|
||||
*/
|
||||
interface SudoModeServiceInterface
|
||||
{
|
||||
/**
|
||||
* Checks the current session to see if sudo mode was activated within the last section of lifetime allocation.
|
||||
*
|
||||
* @return true if sudo mode is currently active
|
||||
*/
|
||||
public function check(Session $session): bool;
|
||||
|
||||
/**
|
||||
* Register activated sudo mode permission in the provided session, which lasts for the configured lifetime.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
public function activate(Session $session): bool;
|
||||
|
||||
/**
|
||||
* How long the sudo mode activation lasts for in minutes.
|
||||
*/
|
||||
public function getLifetime(): int;
|
||||
}
|
84
tests/php/Security/SudoMode/SudoModeServiceTest.php
Normal file
84
tests/php/Security/SudoMode/SudoModeServiceTest.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security\SudoMode\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\Security\SudoMode\SudoModeService;
|
||||
use SilverStripe\Security\SudoMode\SudoModeServiceInterface;
|
||||
|
||||
class SudoModeServiceTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var SudoModeService
|
||||
*/
|
||||
private $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->session = new Session([]);
|
||||
$this->service = new SudoModeService();
|
||||
|
||||
DBDatetime::set_mock_now('2019-03-01 12:00:00');
|
||||
SudoModeService::config()->set('lifetime_minutes', 180);
|
||||
}
|
||||
|
||||
public function testCheckWithoutActivation()
|
||||
{
|
||||
$this->session->clearAll();
|
||||
$this->assertFalse($this->service->check($this->session));
|
||||
}
|
||||
|
||||
public function testCheckWithLastActivationOutsideLifetimeWindow()
|
||||
{
|
||||
// 240 minutes ago
|
||||
$lastActivated = DBDatetime::now()->getTimestamp() - 240 * 60;
|
||||
$this->session->set('sudo-mode-last-activated', $lastActivated);
|
||||
$this->assertFalse($this->service->check($this->session));
|
||||
}
|
||||
|
||||
public function testCheckWithLastActivationInsideLifetimeWindow()
|
||||
{
|
||||
// 25 minutes ago
|
||||
$lastActivated = DBDatetime::now()->getTimestamp() - 25 * 60;
|
||||
$this->session->set('sudo-mode-last-activated', $lastActivated);
|
||||
$this->assertTrue($this->service->check($this->session));
|
||||
}
|
||||
|
||||
public function testActivateAndCheckImmediately()
|
||||
{
|
||||
$this->service->activate($this->session);
|
||||
$this->assertTrue($this->service->check($this->session));
|
||||
}
|
||||
|
||||
public function testSudoModeActivatesOnLogin()
|
||||
{
|
||||
// Sometimes being logged in carries over from other tests
|
||||
$this->logOut();
|
||||
|
||||
/** @var SudoModeServiceInterface $service */
|
||||
$service = Injector::inst()->get(SudoModeServiceInterface::class);
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
|
||||
// Sudo mode should not be enabled automagically when nobody is logged in
|
||||
$this->assertFalse($service->check($session));
|
||||
|
||||
// Ensure sudo mode is activated on login
|
||||
$this->logInWithPermission();
|
||||
$this->assertTrue($service->check($session));
|
||||
|
||||
// Ensure sudo mode is not active after logging out
|
||||
$this->logOut();
|
||||
$this->assertFalse($service->check($session));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user