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:
|
Authenticators:
|
||||||
cms: '%$SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator'
|
cms: '%$SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator'
|
||||||
SilverStripe\Security\IdentityStore: '%$SilverStripe\Security\AuthenticationHandler'
|
SilverStripe\Security\IdentityStore: '%$SilverStripe\Security\AuthenticationHandler'
|
||||||
|
SilverStripe\Security\SudoMode\SudoModeServiceInterface:
|
||||||
|
class: SilverStripe\Security\SudoMode\SudoModeService
|
||||||
|
|
||||||
SilverStripe\Security\PasswordExpirationMiddleware:
|
SilverStripe\Security\PasswordExpirationMiddleware:
|
||||||
default_redirect: Security/changepassword
|
default_redirect: Security/changepassword
|
||||||
|
@ -6,8 +6,11 @@ use SilverStripe\Control\Controller;
|
|||||||
use SilverStripe\Control\Cookie;
|
use SilverStripe\Control\Cookie;
|
||||||
use SilverStripe\Control\Director;
|
use SilverStripe\Control\Director;
|
||||||
use SilverStripe\Control\HTTPRequest;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
|
use SilverStripe\Control\Session;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Security\AuthenticationHandler;
|
use SilverStripe\Security\AuthenticationHandler;
|
||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
|
use SilverStripe\Security\SudoMode\SudoModeServiceInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate a member passed on a session cookie
|
* Authenticate a member passed on a session cookie
|
||||||
@ -72,13 +75,20 @@ class SessionAuthenticationHandler implements AuthenticationHandler
|
|||||||
{
|
{
|
||||||
static::regenerateSessionId();
|
static::regenerateSessionId();
|
||||||
$request = $request ?: Controller::curr()->getRequest();
|
$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
|
// This lets apache rules detect whether the user has logged in
|
||||||
// @todo make this a setting on the authentication handler
|
// @todo make this a setting on the authentication handler
|
||||||
if (Member::config()->get('login_marker_cookie')) {
|
if (Member::config()->get('login_marker_cookie')) {
|
||||||
Cookie::set(Member::config()->get('login_marker_cookie'), 1, 0);
|
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