From 698c495f34b122e1a888c338b8d0cea383a02c5b Mon Sep 17 00:00:00 2001
From: Steve Boyd <emteknetnz@gmail.com>
Date: Mon, 29 Mar 2021 11:43:11 +1300
Subject: [PATCH] NEW Support for session-manager module

---
 .travis.yml                           |  7 +++
 _config/session-manager.yml           |  8 ++++
 code/AuditHookSessionManager.php      | 45 +++++++++++++++++++
 tests/AuditHookSessionManagerTest.php | 64 +++++++++++++++++++++++++++
 4 files changed, 124 insertions(+)
 create mode 100644 _config/session-manager.yml
 create mode 100644 code/AuditHookSessionManager.php
 create mode 100644 tests/AuditHookSessionManagerTest.php

diff --git a/.travis.yml b/.travis.yml
index 0714ba5..4e2178e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,13 @@ jobs:
         - REQUIRE_RECIPE=4.6.x-dev
         - REQUIRE_MFA=4.0.x-dev
         - PHPUNIT_TEST=1
+    - php: 7.4
+      env:
+        - DB=MYSQL
+        - REQUIRE_RECIPE=4.6.x-dev
+        - REQUIRE_SESSION_MANAGER=1.x-dev
+        - PHPUNIT_TEST=1
 
 before_script:
   - if [[ $REQUIRE_MFA ]]; then composer require --no-update silverstripe/mfa:"$REQUIRE_MFA"; fi
+  - if [[ $REQUIRE_SESSION_MANAGER ]]; then composer require --no-update silverstripe/session-manager:"$REQUIRE_SESSION_MANAGER"; fi
diff --git a/_config/session-manager.yml b/_config/session-manager.yml
new file mode 100644
index 0000000..289680f
--- /dev/null
+++ b/_config/session-manager.yml
@@ -0,0 +1,8 @@
+---
+Name: auditor-session-manager
+Only:
+  moduleexists: silverstripe/session-manager
+---
+SilverStripe\SessionManager\Control\LoginSessionController:
+  extensions:
+    - SilverStripe\Auditor\AuditHookSessionManager
diff --git a/code/AuditHookSessionManager.php b/code/AuditHookSessionManager.php
new file mode 100644
index 0000000..4029c5e
--- /dev/null
+++ b/code/AuditHookSessionManager.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace SilverStripe\Auditor;
+
+use Psr\Log\LoggerInterface;
+use SilverStripe\Core\Injector\Injector;
+use SilverStripe\ORM\DataExtension;
+use SilverStripe\Security\Security;
+use SilverStripe\SessionManager\Model\LoginSession;
+
+/**
+ * Provides logging actions on extension hooks from certain silverstripe/session-manager actions.
+ */
+class AuditHookSessionManager extends DataExtension
+{
+    /**
+     * Login session for a member is being removed
+     *
+     * @param LoginSession $loginSession
+     */
+    public function onBeforeRemoveLoginSession(LoginSession $loginSession)
+    {
+        $member = $loginSession->Member();
+        $currentUser = Security::getCurrentUser();
+        if (is_null($member) || $member->ID === 0 || is_null($currentUser) || $currentUser->ID === 0) {
+            return;
+        }
+        $this->getAuditLogger()->info(sprintf(
+            'Login session (ID: %s) for Member "%s" (ID: %s) is being removed by Member "%s" (ID: %s)',
+            $loginSession->ID,
+            $member->Email ?: $member->Title,
+            $member->ID,
+            $currentUser->Email ?: $currentUser->Title,
+            $currentUser->ID
+        ));
+    }
+
+    /**
+     * @return LoggerInterface
+     */
+    protected function getAuditLogger()
+    {
+        return Injector::inst()->get('AuditLogger');
+    }
+}
diff --git a/tests/AuditHookSessionManagerTest.php b/tests/AuditHookSessionManagerTest.php
new file mode 100644
index 0000000..733aaee
--- /dev/null
+++ b/tests/AuditHookSessionManagerTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace SilverStripe\Auditor\Tests;
+
+use SilverStripe\Auditor\Tests\AuditHookTest\Logger;
+use SilverStripe\Control\Controller;
+use SilverStripe\Control\HTTPRequest;
+use SilverStripe\Core\Injector\Injector;
+use SilverStripe\Dev\SapphireTest;
+use SilverStripe\Security\Member;
+use SilverStripe\Security\Security;
+use SilverStripe\Security\SecurityToken;
+use SilverStripe\SessionManager\Control\LoginSessionController;
+use SilverStripe\SessionManager\Model\LoginSession;
+
+class AuditHookSessionManagerTest extends SapphireTest
+{
+    protected $usesDatabase = true;
+
+    /**
+     * @var Logger
+     */
+    protected $writer = null;
+
+    protected function setUp()
+    {
+        parent::setUp();
+        if (!class_exists(LoginSessionController::class)) {
+            $this->markTestSkipped('This test requires the silverstripe/session-manager module to be installed');
+            return;
+        }
+        $this->writer = new Logger;
+        Injector::inst()->unregisterNamedObject('AuditLogger');
+        Injector::inst()->registerService($this->writer, 'AuditLogger');
+    }
+
+    public function testOnBeforeRemoveLoginSession()
+    {
+        $this->logInWithPermission('ADMIN');
+
+        $currentUser = Security::getCurrentUser();
+
+        $member = new Member(array('FirstName' => 'Joe', 'Email' => 'joe3'));
+        $member->write();
+        $request = Controller::curr()->getRequest();
+        $loginSession = LoginSession::generate($member, false, $request);
+
+        SecurityToken::disable();
+        $mockRequest = new HTTPRequest('DELETE', '');
+        $mockRequest->setRouteParams(['ID' => $loginSession->ID]);
+        $controller = new LoginSessionController();
+        $controller->removeLoginSession($mockRequest);
+
+        $message = sprintf(
+            'Login session (ID: %s) for Member "%s" (ID: %s) is being removed by Member "%s" (ID: %s)',
+            $loginSession->ID,
+            $member->Email,
+            $member->ID,
+            $currentUser->Email,
+            $currentUser->ID
+        );
+        $this->assertContains($message, $this->writer->getLastMessage());
+    }
+}