Skip to content

Commit

Permalink
BUG Always validate Member credentials against DRAFT stage (#9671)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxime Rainville committed Sep 7, 2020
1 parent 1a0c80c commit adaf793
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/Security/MemberAuthenticator/MemberAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use SilverStripe\Security\Member;
use SilverStripe\Security\PasswordEncryptor;
use SilverStripe\Security\Security;
use SilverStripe\Versioned\Versioned;

/**
* Authenticator for the default "member" method
Expand All @@ -33,7 +34,15 @@ public function supportedServices()
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
{
// Find authenticated member
$member = $this->authenticateMember($data, $result);
if (class_exists(Versioned::class)) {
[$member, $result] = Versioned::withVersionedMode(function () use ($data) {
Versioned::set_stage(Versioned::DRAFT);
$member = $this->authenticateMember($data, $result);
return [$member, $result];
});
} else {
$member = $this->authenticateMember($data, $result);
}

// Optionally record every login attempt as a {@link LoginAttempt} object
$this->recordLoginAttempt($data, $request, $member, $result->isValid());
Expand Down
188 changes: 188 additions & 0 deletions tests/php/Security/VersionedMemberAuthenticatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php

namespace SilverStripe\Security\Tests;

use SilverStripe\Control\Controller;
use SilverStripe\Control\NullHTTPRequest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Authenticator;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Security\IdentityStore;
use SilverStripe\Security\LoginAttempt;
use SilverStripe\Security\Member;
use SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator;
use SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
use SilverStripe\Security\MemberAuthenticator\MemberLoginForm;
use SilverStripe\Security\PasswordValidator;
use SilverStripe\Security\Security;
use SilverStripe\Versioned\Versioned;

/**
* @skipUpgrade
*/
class VersionedMemberAuthenticatorTest extends SapphireTest
{

protected $usesDatabase = true;

protected static $required_extensions = [
Member::class => [
Versioned::class
]
];

public function setUp()
{
parent::setUp();

if (!class_exists(Versioned::class)) {
$this->markTestSkipped("Versioned is required");
return;
}
}

protected function tearDown()
{
$this->logOut();
parent::tearDown();
}

public function testAuthenticate()
{
$mockDate1 = '2010-01-01 10:00:00';
$readingMode = sprintf('Archive.%s.Stage', $mockDate1);

/** @var Member $member */
$member = DBDatetime::withFixedNow($mockDate1, function () {
$member = Member::create();
$member->update([
'FirstName' => 'Jane',
'Surname' => 'Doe',
'Email' => 'jane.doe@example.com'
]);
$member->write();
$member->changePassword('password', true);

return $member;
});

$member->changePassword('new-password', true);

/** @var ValidationResult $results */
$results = Versioned::withVersionedMode(function () use ($readingMode) {
Versioned::set_reading_mode($readingMode);
$authenticator = new MemberAuthenticator();

// Test correct login
/** @var ValidationResult $message */
$authenticator->authenticate(
[
'Email' => 'jane.doe@example.com',
'Password' => 'password'
],
Controller::curr()->getRequest(),
$result
);

return $result;
});

$this->assertFalse(
$results->isValid(),
'Authenticate using old credentials fails even when using an old reading mode'
);

/** @var ValidationResult $results */
$results = Versioned::withVersionedMode(function () use ($readingMode) {
Versioned::set_reading_mode($readingMode);
$authenticator = new MemberAuthenticator();

// Test correct login
/** @var ValidationResult $message */
$authenticator->authenticate(
[
'Email' => 'jane.doe@example.com',
'Password' => 'new-password'
],
Controller::curr()->getRequest(),
$result
);

return $result;
});

$this->assertTrue(
$results->isValid(),
'Authenticate using current credentials succeeds even when using an old reading mode'
);
}

public function testAuthenticateAgainstLiveStage()
{
/** @var Member $member */
$member = Member::create();
$member->update([
'FirstName' => 'Jane',
'Surname' => 'Doe',
'Email' => 'jane.doe@example.com'
]);
$member->write();
$member->changePassword('password', true);
$member->publishSingle();

$member->changePassword('new-password', true);

/** @var ValidationResult $results */
$results = Versioned::withVersionedMode(function () {
Versioned::set_stage(Versioned::LIVE);
$authenticator = new MemberAuthenticator();

// Test correct login
/** @var ValidationResult $message */
$authenticator->authenticate(
[
'Email' => 'jane.doe@example.com',
'Password' => 'password'
],
Controller::curr()->getRequest(),
$result
);

return $result;
});

$this->assertFalse(
$results->isValid(),
'Authenticate using "published" credentials fails when draft credentials have changed'
);

/** @var ValidationResult $results */
$results = Versioned::withVersionedMode(function () {
Versioned::set_stage(Versioned::LIVE);
$authenticator = new MemberAuthenticator();

// Test correct login
/** @var ValidationResult $message */
$authenticator->authenticate(
[
'Email' => 'jane.doe@example.com',
'Password' => 'new-password'
],
Controller::curr()->getRequest(),
$result
);

return $result;
});

$this->assertTrue(
$results->isValid(),
'Authenticate using current credentials succeeds even when "published" credentials are different'
);
}
}

0 comments on commit adaf793

Please sign in to comment.