Skip to content

Commit

Permalink
Merge pull request #1 from dnadesign/feature/unit-tests
Browse files Browse the repository at this point in the history
Add unit test
  • Loading branch information
jules0x authored Feb 7, 2024
2 parents f0ebc16 + 0cc1408 commit 4fa66c9
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 38 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"autoload": {
"psr-4": {
"DNADesign\\Tagurit\\": "src/"
"DNADesign\\IdleLock\\": "src/"
}
}
}
78 changes: 43 additions & 35 deletions src/Extensions/MemberLockoutExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;

class MemberLockoutExtension extends DataExtension
Expand Down Expand Up @@ -33,25 +34,6 @@ public function canLogIn(&$result)
}
}

/**
* Return either the access date of the most recent LoginSession, or the created date if there are none
*
* @return string
*/
public function getLastAccessed()
{
// Default if there are no login sessions
$lastAccessed = sprintf('New: %s ', $this->owner->LastEdited);

// Check for LoginSessions, and overwrite the default "Last" value
if ($this->owner->LoginSessions()->exists()) {
$latestLoginSession = $this->owner->LoginSessions()->sort('LastAccessed', 'DESC')->first();
$lastAccessed = $latestLoginSession->LastAccessed;
}

return $lastAccessed;
}

/**
* List the groups this Member is a member of
*
Expand All @@ -68,34 +50,60 @@ public function getGroupNames()
}

/**
* Lock the user if their last login was more than the lockout threshold ago
*
* @return void
* Return the Date after which a user should be locked out
*/
public function IdleUserLock()
public function getLockIfInactiveAfter() : DBDateTime
{
// Global lockout threshold
$defaultLockoutThreshold = Config::inst()->get(Member::class, 'lockout_threshold_days');
$lowestThreshold = $defaultLockoutThreshold;

// Get the lowest non-0 threshold from this Members Groups, and format it for compare
$groups = $this->owner->Groups();
$lowestThreshold = $groups->filter('LockoutThresholdDays:GreaterThan', 0)->min('LockoutThresholdDays') ?: $lowestThreshold;
$thresholdDateTime = date('Y-m-d H:i:s', strtotime("-{$lowestThreshold} days"));
$lowestThreshold = $groups->filter([
'LockoutThresholdDays:GreaterThan' => 0,
'LockoutThresholdDays:LessThan' => $defaultLockoutThreshold,
])->min('LockoutThresholdDays') ?: $defaultLockoutThreshold;

return DBDateTime::now()->modify("-{$lowestThreshold} days");
}

/**
* Return the date the user has last accessed the CMS
*/
public function getLastAccessed() : DBDatetime
{
// Default "Last"; i.e. accounts for new users who haven't yet logged in
$lastAccessed = $this->owner->LastEdited;
$lastAccessed = $this->owner->dbObject('LastEdited');

// Check for LoginSessions, and overwrite the default "Last" value
if ($this->owner->LoginSessions()->exists()) {
$latestLoginSession = $this->owner->LoginSessions()->sort('LastAccessed', 'DESC')->first();
$lastAccessed = $latestLoginSession->LastAccessed;
$lastAccessed = $latestLoginSession->dbObject('LastAccessed');
}

// If the threshold is met, lock the Member account
if ($thresholdDateTime > $lastAccessed) {
$this->owner->Locked = true;
$this->owner->write();
}
return $lastAccessed;
}

/**
* Return whether the user should be locked out
* Depending on whether they haven't logged in for a certain amount of time.
*/
public function shouldBeLockedOut() : bool
{
$lockIfInactiveAfter = $this->owner->getLockIfInactiveAfter();
$lastAccessed = $this->owner->getLastAccessed();

return $lockIfInactiveAfter > $lastAccessed;
}

/**
* Lock the user if their last login was more than the lockout threshold ago
*
* @return boolean
*/
public function doLockOutAfterIdle() : bool
{
$this->owner->Locked = true;
$this->owner->write();

return true;
}
}
11 changes: 9 additions & 2 deletions src/Tasks/LockMembersTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@

namespace DNADesign\IdleLock\Tasks;

use Psr\Log\LoggerInterface;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\BuildTask;
use SilverStripe\Security\Member;

class LockMembersTask extends BuildTask
{
protected $title = 'Lock Members Task';

protected $description = 'Locks member accounts based on last login time. Threshold determined per security Group.';
protected $description = 'Locks member accounts based on last login time. Lockout threshold determined per security Group.';

public function run($request)
{
Injector::inst()->get(LoggerInterface::class)->info('Check for idle member accounts...');

foreach (Member::get() as $member) {
$member->IdleUserLock();
if ($member->shouldBeLockedOut()) {
$member->doLockOutAfterIdle();
Injector::inst()->get(LoggerInterface::class)->info(sprintf('Member %s (%s) is now locked out of the CMS after reaching the idle lockout threshold', $member->getTitle(), $member->ID));
}
}

exit;
Expand Down
56 changes: 56 additions & 0 deletions src/Tasks/UnitTestTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace DNADesign\IdleLock\Tasks;

use SilverStripe\Dev\BuildTask;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;

/**
* This is in lieu of proper unit test as I can't seem to be able to make them work.
* We'll revisit when we ge the time.
*/
class UnitTestTask extends BuildTask
{
private static $segment = 'idlelock-unittest';

protected $enabled = false;

public function run($request)
{
echo 'testDefaultThreshold => '.$this->testDefaultThreshold().PHP_EOL;
echo 'testNotLockedOut => '.$this->testNotLockedOut().PHP_EOL;
echo 'testLockedOut => '.$this->testLockedOut().PHP_EOL;

echo 'Done.';
}

private function testDefaultThreshold()
{
$threshold = (int) Member::config()->get('lockout_threshold_days');
return 30 === $threshold;
}

private function testNotLockedOut()
{
DBDatetime::set_mock_now('2024-02-01 10:00:00');

$member = new Member();
$member->LastEdited = '2024-01-15 10:00:00';

return $member->shouldBeLockedOut() === false;
}

private function testLockedOut()
{
DBDatetime::set_mock_now('2024-02-01 10:00:00');

$member = new Member();
$member->LastEdited = '2023-01-01 10:00:00';

return $member->shouldBeLockedOut() === true;
}

// NOTE: cannot test the group threshold as it would require
// writing in the database.
}
32 changes: 32 additions & 0 deletions tests/IdleLockTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
namespace DNADesign\IdleLock\Tests;

use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;

/**
* NOTE: at time of writing, I couldn't get the test to run at all
* Seems to be a widespread issue.
*/
class IdleLockTest extends SapphireTest
{
/**
* Defines the fixture file to use for this test class
* @var string $fixture_file
*/
protected static $fixture_file = 'dnadesign/silverstripe-idlelock:tests/fixtures.yml';

protected function setUp() : void
{
DBDatetime::set_mock_now('2024-02-01 10:00:00');
}

public function testDefaultThreshold()
{
$threshold = (int) Member::config()->get('lockout_threshold_days');
$this->assertEquals(30, $threshold);
}

// TODO: Add other tests to check every functions
}
24 changes: 24 additions & 0 deletions tests/fixtures.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
SilverStripe\Security\Group:
lowThreshold:
Title: Low threshold
LockoutThresholdDays: 10

SilverStripe\Security\Member:
user1:
FirstName: unlocked
Email: [email protected]
LastEdited: '2024-02-01 10:00:00'
user2:
FirstName: locked
Email: [email protected]
LastEdited: '2024-02-01 10:00:00'
user3:
FirstName: groupLocked
Email: [email protected]
LastEdited: '2024-02-01 10:00:00'
Groups: =>SilverStripe\Security\Group.lowThreshold

SilverStripe\SessionManager\Models\LoginSession:
user2session:
LastAccessed: '2023-02-01 10:00:00'
Member: =>SilverStripe\Security\Member.user2

0 comments on commit 4fa66c9

Please sign in to comment.