From fecb7ba4d8341e21c8d95d6cfe7bbf1095281645 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Mon, 30 Jan 2023 12:51:38 +1300 Subject: [PATCH] NEW Add sudo mode service --- _config/security.yml | 2 + .../SessionAuthenticationHandler.php | 12 ++- src/Security/SudoMode/SudoModeService.php | 52 ++++++++++++ .../SudoMode/SudoModeServiceInterface.php | 31 +++++++ .../Security/SudoMode/SudoModeServiceTest.php | 84 +++++++++++++++++++ 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/Security/SudoMode/SudoModeService.php create mode 100644 src/Security/SudoMode/SudoModeServiceInterface.php create mode 100644 tests/php/Security/SudoMode/SudoModeServiceTest.php diff --git a/_config/security.yml b/_config/security.yml index 1dfd8ef92..b7d029973 100644 --- a/_config/security.yml +++ b/_config/security.yml @@ -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 diff --git a/src/Security/MemberAuthenticator/SessionAuthenticationHandler.php b/src/Security/MemberAuthenticator/SessionAuthenticationHandler.php index c2fbd399c..97ad8e6ab 100644 --- a/src/Security/MemberAuthenticator/SessionAuthenticationHandler.php +++ b/src/Security/MemberAuthenticator/SessionAuthenticationHandler.php @@ -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); } /** diff --git a/src/Security/SudoMode/SudoModeService.php b/src/Security/SudoMode/SudoModeService.php new file mode 100644 index 000000000..33deb10bf --- /dev/null +++ b/src/Security/SudoMode/SudoModeService.php @@ -0,0 +1,52 @@ +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'); + } +} diff --git a/src/Security/SudoMode/SudoModeServiceInterface.php b/src/Security/SudoMode/SudoModeServiceInterface.php new file mode 100644 index 000000000..11b1ede43 --- /dev/null +++ b/src/Security/SudoMode/SudoModeServiceInterface.php @@ -0,0 +1,31 @@ +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)); + } +}