mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW: Add AuthenticationHandler interface
NEW: Add IdentityStore for registering log-in / log-out data NEW: Add AuthenticationRequestFilter for managing login NEW: Add Security:setCurrentUser() / Security::getCurrentUser() NEW: Add FunctionalTest::logOut()
This commit is contained in:
parent
c4194f0ed2
commit
f9ea752bae
@ -8,5 +8,26 @@ SilverStripe\Security\Security:
|
||||
default: SilverStripe\Security\MemberAuthenticator\Authenticator
|
||||
cms: SilverStripe\Security\MemberAuthenticator\CMSAuthenticator
|
||||
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Control\RequestProcessor:
|
||||
properties:
|
||||
filters:
|
||||
- '%$SilverStripe\Security\AuthenticationRequestFilter'
|
||||
SilverStripe\Security\MemberAuthenticator\SessionAuthenticationHandler:
|
||||
properties:
|
||||
SessionVariable: loggedInAs
|
||||
SilverStripe\Security\MemberAuthenticator\CookieAuthenticationHandler:
|
||||
properties:
|
||||
TokenCookieName: alc_enc
|
||||
DeviceCookieName: alc_device
|
||||
CascadeLogInTo: %$SilverStripe\Security\MemberAuthenticator\SessionAuthenticationHandler
|
||||
SilverStripe\Security\IdentityStore:
|
||||
class: SilverStripe\Security\AuthenticationRequestFilter
|
||||
|
||||
SilverStripe\Security\AuthenticationRequestFilter:
|
||||
handlers:
|
||||
session: SilverStripe\Security\MemberAuthenticator\SessionAuthenticationHandler
|
||||
alc: SilverStripe\Security\MemberAuthenticator\CookieAuthenticationHandler
|
||||
|
||||
SilverStripe\Security\MemberAuthenticator\CMSSecurity:
|
||||
reauth_enabled: true
|
@ -7,6 +7,7 @@ use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Security\BasicAuth;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\SecurityToken;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use PHPUnit_Framework_AssertionFailedError;
|
||||
@ -104,6 +105,9 @@ class FunctionalTest extends SapphireTest
|
||||
// basis.
|
||||
BasicAuth::protect_entire_site(false);
|
||||
|
||||
$this->session()->inst_clear('loggedInAs');
|
||||
Security::setCurrentUser(null);
|
||||
|
||||
SecurityToken::disable();
|
||||
}
|
||||
|
||||
@ -401,15 +405,26 @@ class FunctionalTest extends SapphireTest
|
||||
*/
|
||||
public function logInAs($member)
|
||||
{
|
||||
if (is_object($member)) {
|
||||
$memberID = $member->ID;
|
||||
} elseif (is_numeric($member)) {
|
||||
$memberID = $member;
|
||||
} else {
|
||||
$memberID = $this->idFromFixture('SilverStripe\\Security\\Member', $member);
|
||||
if (is_numeric($member)) {
|
||||
$member = DataObject::get_by_id(Member::class, $member);
|
||||
} elseif (!is_object($member)) {
|
||||
$member = $this->objFromFixture('SilverStripe\\Security\\Member', $member);
|
||||
}
|
||||
|
||||
$this->session()->inst_set('loggedInAs', $memberID);
|
||||
$this->session()->inst_set('loggedInAs', $member->ID);
|
||||
Security::setCurrentUser($member);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log in as the given member
|
||||
*
|
||||
* @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
|
||||
*/
|
||||
public function logOut()
|
||||
{
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
Security::setCurrentUser(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1250,7 +1250,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
|
||||
$this->cache_generatedMembers[$permCode] = $member;
|
||||
}
|
||||
$member->logIn();
|
||||
Security::setCurrentUser($member);
|
||||
return $member->ID;
|
||||
}
|
||||
|
||||
|
42
src/Security/AuthenticationHandler.php
Normal file
42
src/Security/AuthenticationHandler.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\ORM\ValidationException;
|
||||
use SilverStripe\Security\Member;
|
||||
|
||||
/**
|
||||
* An AuthenticationHandler is responsible for providing an identity (in the form of a Member object) for
|
||||
* a given HTTPRequest.
|
||||
*
|
||||
* It should return the authenticated Member if successful. If a Member cannot be found from the current
|
||||
* request it should *not* attempt to redirect the visitor to a log-in from or 3rd party handler, as that
|
||||
* is the responsibiltiy of other systems.
|
||||
*/
|
||||
interface AuthenticationHandler
|
||||
{
|
||||
/**
|
||||
* Given the current request, authenticate the request for non-session authorization (outside the CMS).
|
||||
*
|
||||
* The Member returned from this method will be provided to the Manager for use in the OperationResolver context
|
||||
* in place of the current CMS member.
|
||||
*
|
||||
* Authenticators can be given a priority. In this case, the authenticator with the highest priority will be
|
||||
* returned first. If not provided, it will default to a low number.
|
||||
*
|
||||
* An example for configuring the BasicAuthAuthenticator:
|
||||
*
|
||||
* <code>
|
||||
* SilverStripe\Security\Security:
|
||||
* authentication_handlers:
|
||||
* - SilverStripe\Security\BasicAuthentionHandler
|
||||
* </code>
|
||||
*
|
||||
* @param HTTPRequest $request The current HTTP request
|
||||
* @return Member|null The authenticated Member, or null if this auth mechanism isn't used.
|
||||
* @throws ValidationException If authentication data exists but does not match a member.
|
||||
*/
|
||||
public function authenticateRequest(HTTPRequest $request);
|
||||
}
|
96
src/Security/AuthenticationRequestFilter.php
Normal file
96
src/Security/AuthenticationRequestFilter.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security;
|
||||
|
||||
use SilverStripe\Control\RequestFilter;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
class AuthenticationRequestFilter implements RequestFilter, IdentityStore
|
||||
{
|
||||
|
||||
use Configurable;
|
||||
|
||||
protected function getHandlers()
|
||||
{
|
||||
return array_map(
|
||||
function ($identifier) {
|
||||
return Injector::inst()->get($identifier);
|
||||
},
|
||||
$this->config()->get('handlers')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify the current user from the request
|
||||
*/
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
{
|
||||
try {
|
||||
foreach ($this->getHandlers() as $handler) {
|
||||
// @todo Update requestfilter logic to allow modification of initial response
|
||||
// in order to add cookies, etc
|
||||
$member = $handler->authenticateRequest($request, new HTTPResponse());
|
||||
if ($member) {
|
||||
// @todo Remove the static coupling here
|
||||
Security::setCurrentUser($member);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ValidationException $e) {
|
||||
throw new HTTPResponse_Exception(
|
||||
"Bad log-in details: " . $e->getMessage(),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op
|
||||
*/
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Log into the identity-store handlers attached to this request filter
|
||||
*
|
||||
* @inherit
|
||||
*/
|
||||
public function logIn(Member $member, $persistent, HTTPRequest $request)
|
||||
{
|
||||
// @todo Coupling here isn't ideal.
|
||||
$member->beforeMemberLoggedIn();
|
||||
|
||||
foreach ($this->getHandlers() as $handler) {
|
||||
if ($handler instanceof IdentityStore) {
|
||||
$handler->logIn($member, $persistent, $request);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Coupling here isn't ideal.
|
||||
Security::setCurrentUser($member);
|
||||
$member->afterMemberLoggedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out of all the identity-store handlers attached to this request filter
|
||||
*
|
||||
* @inherit
|
||||
*/
|
||||
public function logOut(HTTPRequest $request)
|
||||
{
|
||||
foreach ($this->getHandlers() as $handler) {
|
||||
if ($handler instanceof IdentityStore) {
|
||||
$handler->logOut($request);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Coupling here isn't ideal.
|
||||
Security::setCurrentUser(null);
|
||||
}
|
||||
}
|
32
src/Security/IdentityStore.php
Normal file
32
src/Security/IdentityStore.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
|
||||
/**
|
||||
* Represents an authentication handler that can have identities logged into & out of it.
|
||||
* For example, SessionAuthenticationHandler is an IdentityStore (as we can write a new member to it)
|
||||
* but BasicAuthAuthenticationHandler is not (as it's up to the browser to handle log-in / log-out)
|
||||
*/
|
||||
interface IdentityStore
|
||||
{
|
||||
/**
|
||||
* Log the given member into this identity store.
|
||||
*
|
||||
* @param $member The member to log in.
|
||||
* @param $persistent boolean If set to true, the login may persist beyond the current session.
|
||||
* @param $request The request of the visitor that is logging in, to get, for example, cookies.
|
||||
* @param $response The response object to modify, if needed.
|
||||
*/
|
||||
public function logIn(Member $member, $persistent, HTTPRequest $request);
|
||||
|
||||
/**
|
||||
* Log any logged-in member out of this identity store.
|
||||
*
|
||||
* @param $request The request of the visitor that is logging out, to get, for example, cookies.
|
||||
* @param $response The response object to modify, if needed.
|
||||
*/
|
||||
public function logOut(HTTPRequest $request);
|
||||
}
|
@ -390,33 +390,6 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
return DBDatetime::now()->getTimestamp() < $this->dbObject('LockedOutUntil')->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the session_id.
|
||||
* This wrapper is here to make it easier to disable calls to session_regenerate_id(), should you need to.
|
||||
* They have caused problems in certain
|
||||
* quirky problems (such as using the Windmill 0.3.6 proxy).
|
||||
*/
|
||||
public static function session_regenerate_id()
|
||||
{
|
||||
if (!self::config()->session_regenerate_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This can be called via CLI during testing.
|
||||
if (Director::is_cli()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = '';
|
||||
$line = '';
|
||||
|
||||
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
|
||||
// There's nothing we can do about this, because it's an operating system function!
|
||||
if (!headers_sent($file, $line)) {
|
||||
@session_regenerate_id(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link PasswordValidator} object to use to validate member's passwords.
|
||||
*
|
||||
@ -447,48 +420,30 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs this member in
|
||||
* @deprecated Use Security::setCurrentUser() or IdentityStore::logIn()
|
||||
*
|
||||
* @param bool $remember If set to TRUE, the member will be logged in automatically the next time.
|
||||
*/
|
||||
public function logIn($remember = false)
|
||||
public function logIn()
|
||||
{
|
||||
user_error("This method is deprecated and now only logs in for the current request", E_USER_WARNING);
|
||||
Security::setCurrentUser($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a member is logged in via session/cookie/etc
|
||||
*/
|
||||
public function beforeMemberLoggedIn()
|
||||
{
|
||||
// @todo Move to middleware on the AuthenticationRequestFilter IdentityStore
|
||||
$this->extend('beforeMemberLoggedIn');
|
||||
|
||||
self::session_regenerate_id();
|
||||
|
||||
Session::set("loggedInAs", $this->ID);
|
||||
// This lets apache rules detect whether the user has logged in
|
||||
if (Member::config()->login_marker_cookie) {
|
||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0);
|
||||
}
|
||||
|
||||
if (Security::config()->autologin_enabled) {
|
||||
// Cleans up any potential previous hash for this member on this device
|
||||
if ($alcDevice = Cookie::get('alc_device')) {
|
||||
RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
|
||||
}
|
||||
if ($remember) {
|
||||
$rememberLoginHash = RememberLoginHash::generate($this);
|
||||
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
||||
$deviceExpiryDays = RememberLoginHash::config()->uninherited('device_expiry_days');
|
||||
Cookie::set(
|
||||
'alc_enc',
|
||||
$this->ID . ':' . $rememberLoginHash->getToken(),
|
||||
$tokenExpiryDays,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
);
|
||||
Cookie::set('alc_device', $rememberLoginHash->DeviceID, $deviceExpiryDays, null, null, null, true);
|
||||
} else {
|
||||
Cookie::set('alc_enc', null);
|
||||
Cookie::set('alc_device', null);
|
||||
Cookie::force_expiry('alc_enc');
|
||||
Cookie::force_expiry('alc_device');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called after a member is logged in via session/cookie/etc
|
||||
*/
|
||||
public function afterMemberLoggedIn()
|
||||
{
|
||||
// Clear the incorrect log-in count
|
||||
$this->registerSuccessfulLogin();
|
||||
|
||||
@ -539,91 +494,37 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user in if the "remember login" cookie is set
|
||||
*
|
||||
* The <i>remember login token</i> will be changed on every successful
|
||||
* auto-login.
|
||||
* Logs this member out.
|
||||
*/
|
||||
public static function autoLogin()
|
||||
public function logOut()
|
||||
{
|
||||
// Don't bother trying this multiple times
|
||||
if (!class_exists(SapphireTest::class, false) || !SapphireTest::is_running_test()) {
|
||||
self::$_already_tried_to_auto_log_in = true;
|
||||
}
|
||||
$this->extend('beforeMemberLoggedOut');
|
||||
|
||||
if (!Security::config()->autologin_enabled
|
||||
|| strpos(Cookie::get('alc_enc'), ':') === false
|
||||
|| Session::get("loggedInAs")
|
||||
|| !Security::database_is_ready()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strpos(Cookie::get('alc_enc'), ':') && Cookie::get('alc_device') && !Session::get("loggedInAs")) {
|
||||
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
||||
|
||||
if (!$uid || !$token) {
|
||||
return;
|
||||
}
|
||||
|
||||
$deviceID = Cookie::get('alc_device');
|
||||
|
||||
/** @var Member $member */
|
||||
$member = Member::get()->byID($uid);
|
||||
|
||||
/** @var RememberLoginHash $rememberLoginHash */
|
||||
$rememberLoginHash = null;
|
||||
|
||||
// check if autologin token matches
|
||||
if ($member) {
|
||||
$hash = $member->encryptWithUserSettings($token);
|
||||
$rememberLoginHash = RememberLoginHash::get()
|
||||
->filter(array(
|
||||
'MemberID' => $member->ID,
|
||||
'DeviceID' => $deviceID,
|
||||
'Hash' => $hash
|
||||
))->first();
|
||||
if (!$rememberLoginHash) {
|
||||
$member = null;
|
||||
} else {
|
||||
// Check for expired token
|
||||
$expiryDate = new DateTime($rememberLoginHash->ExpiryDate);
|
||||
$now = DBDatetime::now();
|
||||
$now = new DateTime($now->Rfc2822());
|
||||
if ($now > $expiryDate) {
|
||||
$member = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($member) {
|
||||
self::session_regenerate_id();
|
||||
Session::set("loggedInAs", $member->ID);
|
||||
// This lets apache rules detect whether the user has logged in
|
||||
Session::clear("loggedInAs");
|
||||
if (Member::config()->login_marker_cookie) {
|
||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
||||
Cookie::set(Member::config()->login_marker_cookie, null, 0);
|
||||
}
|
||||
|
||||
if ($rememberLoginHash) {
|
||||
$rememberLoginHash->renew();
|
||||
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
||||
Cookie::set(
|
||||
'alc_enc',
|
||||
$member->ID . ':' . $rememberLoginHash->getToken(),
|
||||
$tokenExpiryDays,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
Session::destroy();
|
||||
|
||||
$member->write();
|
||||
$this->extend('memberLoggedOut');
|
||||
|
||||
// Clears any potential previous hashes for this member
|
||||
RememberLoginHash::clear($this, Cookie::get('alc_device'));
|
||||
|
||||
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
||||
Cookie::force_expiry('alc_enc');
|
||||
Cookie::set('alc_device', null);
|
||||
Cookie::force_expiry('alc_device');
|
||||
|
||||
// Switch back to live in order to avoid infinite loops when
|
||||
// redirecting to the login screen (if this login screen is versioned)
|
||||
Session::clear('readingMode');
|
||||
|
||||
$this->write();
|
||||
|
||||
// Audit logging hook
|
||||
$member->extend('memberAutoLoggedIn');
|
||||
}
|
||||
}
|
||||
$this->extend('memberLoggedOut');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -820,19 +721,8 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function currentUser()
|
||||
{
|
||||
$id = Member::currentUserID();
|
||||
|
||||
if ($id) {
|
||||
return DataObject::get_by_id(Member::class, $id);
|
||||
return Security::getCurrentUser();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow override of the current user ID
|
||||
*
|
||||
* @var int|null Set to null to fallback to session, or an explicit ID
|
||||
*/
|
||||
protected static $overrideID = null;
|
||||
|
||||
/**
|
||||
* Temporarily act as the specified user, limited to a $callback, but
|
||||
@ -851,13 +741,18 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function actAs($member, $callback)
|
||||
{
|
||||
$id = ($member instanceof Member ? $member->ID : $member) ?: 0;
|
||||
$previousID = static::$overrideID;
|
||||
static::$overrideID = $id;
|
||||
$previousUser = Security::getCurrentUser();
|
||||
|
||||
// Transform ID to member
|
||||
if (is_numeric($member)) {
|
||||
$member = DataObject::get_by_id(Member::class, $member);
|
||||
}
|
||||
Security::setCurrentUser($member);
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::$overrideID = $previousID;
|
||||
Security::setCurrentUser($previousUser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -868,22 +763,13 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function currentUserID()
|
||||
{
|
||||
if (isset(static::$overrideID)) {
|
||||
return static::$overrideID;
|
||||
if ($member = Security::getCurrentUser()) {
|
||||
return $member->ID;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$id = Session::get("loggedInAs");
|
||||
if (!$id && !self::$_already_tried_to_auto_log_in) {
|
||||
self::autoLogin();
|
||||
$id = Session::get("loggedInAs");
|
||||
}
|
||||
|
||||
return is_numeric($id) ? $id : 0;
|
||||
}
|
||||
|
||||
private static $_already_tried_to_auto_log_in = false;
|
||||
|
||||
|
||||
/*
|
||||
* Generate a random password, with randomiser to kick in if there's no words file on the
|
||||
* filesystem.
|
||||
|
@ -3,12 +3,13 @@
|
||||
|
||||
namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Forms\FormRequestHandler;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
|
||||
class ChangePasswordHandler extends FormRequestHandler
|
||||
{
|
||||
@ -18,7 +19,7 @@ class ChangePasswordHandler extends FormRequestHandler
|
||||
* @param array $data The user submitted data
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function doChangePassword(array $data)
|
||||
public function doChangePassword(array $data, $form)
|
||||
{
|
||||
$member = Member::currentUser();
|
||||
// The user was logged in, check the current password
|
||||
@ -80,7 +81,8 @@ class ChangePasswordHandler extends FormRequestHandler
|
||||
$member->write();
|
||||
|
||||
if ($member->canLogIn()->isValid()) {
|
||||
$member->logIn();
|
||||
Injector::inst()->get(IdentityStore::class)
|
||||
->logIn($member, false, $form->getRequestHandler()->getRequest());
|
||||
}
|
||||
|
||||
// TODO Add confirmation message to login redirect
|
||||
|
223
src/Security/MemberAuthenticator/CookieAuthenticationHandler.php
Normal file
223
src/Security/MemberAuthenticator/CookieAuthenticationHandler.php
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Security\AuthenticationHandler as AuthenticationHandlerInterface;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Security\RememberLoginHash;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\Control\Cookie;
|
||||
|
||||
/**
|
||||
* Authenticate a member pased on a session cookie
|
||||
*/
|
||||
class CookieAuthenticationHandler implements AuthenticationHandlerInterface, IdentityStore
|
||||
{
|
||||
|
||||
private $deviceCookieName;
|
||||
private $tokenCookieName;
|
||||
private $cascadeLogInTo;
|
||||
|
||||
/**
|
||||
* Get the name of the cookie used to track this device
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDeviceCookieName()
|
||||
{
|
||||
return $this->deviceCookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the cookie used to track this device
|
||||
*
|
||||
* @param string $cookieName
|
||||
* @return null
|
||||
*/
|
||||
public function setDeviceCookieName($deviceCookieName)
|
||||
{
|
||||
$this->deviceCookieName = $deviceCookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the cookie used to store an login token
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTokenCookieName()
|
||||
{
|
||||
return $this->tokenCookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the cookie used to store an login token
|
||||
*
|
||||
* @param string $cookieName
|
||||
* @return null
|
||||
*/
|
||||
public function setTokenCookieName($tokenCookieName)
|
||||
{
|
||||
$this->tokenCookieName = $tokenCookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once a member is found by authenticateRequest() pass it to this identity store
|
||||
*
|
||||
* @return IdentityStore
|
||||
*/
|
||||
public function getCascadeLogInTo()
|
||||
{
|
||||
return $this->cascadeLogInTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the cookie used to store an login token
|
||||
*
|
||||
* @param $cascadeLogInTo
|
||||
* @return null
|
||||
*/
|
||||
public function setCascadeLogInTo(IdentityStore $cascadeLogInTo)
|
||||
{
|
||||
$this->cascadeLogInTo = $cascadeLogInTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function authenticateRequest(HTTPRequest $request)
|
||||
{
|
||||
$uidAndToken = Cookie::get($this->getTokenCookieName());
|
||||
$deviceID = Cookie::get($this->getDeviceCookieName());
|
||||
|
||||
// @todo Consider better placement of database_is_ready test
|
||||
if (!$deviceID || strpos($uidAndToken, ':') === false || !Security::database_is_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($uid, $token) = explode(':', $uidAndToken, 2);
|
||||
|
||||
if (!$uid || !$token) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Member $member */
|
||||
$member = Member::get()->byID($uid);
|
||||
|
||||
/** @var RememberLoginHash $rememberLoginHash */
|
||||
$rememberLoginHash = null;
|
||||
|
||||
// check if autologin token matches
|
||||
if ($member) {
|
||||
$hash = $member->encryptWithUserSettings($token);
|
||||
$rememberLoginHash = RememberLoginHash::get()
|
||||
->filter(array(
|
||||
'MemberID' => $member->ID,
|
||||
'DeviceID' => $deviceID,
|
||||
'Hash' => $hash
|
||||
))->first();
|
||||
|
||||
if (!$rememberLoginHash) {
|
||||
$member = null;
|
||||
} else {
|
||||
// Check for expired token
|
||||
$expiryDate = new \DateTime($rememberLoginHash->ExpiryDate);
|
||||
$now = DBDatetime::now();
|
||||
$now = new \DateTime($now->Rfc2822());
|
||||
if ($now > $expiryDate) {
|
||||
$member = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($member) {
|
||||
if ($this->cascadeLogInTo) {
|
||||
// @todo look at how to block "regular login" triggers from happening here
|
||||
// @todo deal with the fact that the Session::current_session() isn't correct here :-/
|
||||
$this->cascadeLogInTo->logIn($member, false, $request);
|
||||
//\SilverStripe\Dev\Debug::message('here');
|
||||
}
|
||||
|
||||
// @todo Consider whether response should be part of logIn() as well
|
||||
|
||||
// Renew the token
|
||||
if ($rememberLoginHash) {
|
||||
$rememberLoginHash->renew();
|
||||
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
||||
Cookie::set(
|
||||
$this->getTokenCookieName(),
|
||||
$member->ID . ':' . $rememberLoginHash->getToken(),
|
||||
$tokenExpiryDays,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return $member;
|
||||
|
||||
// Audit logging hook
|
||||
$member->extend('memberAutoLoggedIn');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function logIn(Member $member, $persistent, HTTPRequest $request)
|
||||
{
|
||||
// @todo couple the cookies to the response object
|
||||
|
||||
// Cleans up any potential previous hash for this member on this device
|
||||
if ($alcDevice = Cookie::get($this->getDeviceCookieName())) {
|
||||
RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
|
||||
}
|
||||
|
||||
// Set a cookie for persistent log-ins
|
||||
if ($persistent) {
|
||||
$rememberLoginHash = RememberLoginHash::generate($member);
|
||||
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
||||
$deviceExpiryDays = RememberLoginHash::config()->uninherited('device_expiry_days');
|
||||
Cookie::set(
|
||||
$this->getTokenCookieName(),
|
||||
$member->ID . ':' . $rememberLoginHash->getToken(),
|
||||
$tokenExpiryDays,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
);
|
||||
Cookie::set(
|
||||
$this->getDeviceCookieName(),
|
||||
$rememberLoginHash->DeviceID,
|
||||
$deviceExpiryDays,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
// Clear a cookie for non-persistent log-ins
|
||||
} else {
|
||||
$this->logOut($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function logOut(HTTPRequest $request)
|
||||
{
|
||||
// @todo couple the cookies to the response object
|
||||
|
||||
Cookie::set($this->getTokenCookieName(), null);
|
||||
Cookie::set($this->getDeviceCookieName(), null);
|
||||
Cookie::force_expiry($this->getTokenCookieName());
|
||||
Cookie::force_expiry($this->getDeviceCookieName());
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
|
||||
/**
|
||||
* Handle login requests from MemberLoginForm
|
||||
@ -99,7 +101,7 @@ class LoginHandler extends RequestHandler
|
||||
|
||||
// Successful login
|
||||
if ($member = $this->checkLogin($data, $failureMessage)) {
|
||||
$this->performLogin($member, $data);
|
||||
$this->performLogin($member, $data, $form->getRequestHandler()->getRequest());
|
||||
return $this->redirectAfterSuccessfulLogin();
|
||||
}
|
||||
|
||||
@ -220,9 +222,10 @@ class LoginHandler extends RequestHandler
|
||||
* @return Member Returns the member object on successful authentication
|
||||
* or NULL on failure.
|
||||
*/
|
||||
public function performLogin($member, $data)
|
||||
public function performLogin($member, $data, $request)
|
||||
{
|
||||
$member->logIn(isset($data['Remember']));
|
||||
// @todo pass request/response
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($member, !empty($data['Remember']), $request);
|
||||
return $member;
|
||||
}
|
||||
/**
|
||||
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Security\AuthenticationHandler as AuthenticationHandlerInterface;
|
||||
use SilverStripe\ORM\ValidationException;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
|
||||
/**
|
||||
* Authenticate a member pased on a session cookie
|
||||
*/
|
||||
class SessionAuthenticationHandler implements AuthenticationHandlerInterface, IdentityStore
|
||||
{
|
||||
|
||||
private $sessionVariable;
|
||||
|
||||
/**
|
||||
* Get the session variable name used to track member ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionVariable()
|
||||
{
|
||||
return $this->sessionVariable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the session variable name used to track member ID
|
||||
*
|
||||
* @param string $sessionVariable
|
||||
* @return null
|
||||
*/
|
||||
public function setSessionVariable($sessionVariable)
|
||||
{
|
||||
$this->sessionVariable = $sessionVariable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function authenticateRequest(HTTPRequest $request)
|
||||
{
|
||||
// @todo couple the session to a request object
|
||||
// $session = $request->getSession();
|
||||
|
||||
if ($id = Session::get($this->getSessionVariable())) {
|
||||
// If ID is a bad ID it will be treated as if the user is not logged in, rather than throwing a
|
||||
// ValidationException
|
||||
return DataObject::get_by_id(Member::class, $id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function logIn(Member $member, $persistent, HTTPRequest $request)
|
||||
{
|
||||
// @todo couple the session to a request object
|
||||
// $session = $request->getSession();
|
||||
|
||||
$this->regenerateSessionId();
|
||||
Session::set($this->getSessionVariable(), $member->ID);
|
||||
|
||||
// This lets apache rules detect whether the user has logged in
|
||||
// @todo make this a settign on the authentication handler
|
||||
if (Member::config()->login_marker_cookie) {
|
||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the session_id.
|
||||
*/
|
||||
protected static function regenerateSessionId()
|
||||
{
|
||||
if (!Member::config()->session_regenerate_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This can be called via CLI during testing.
|
||||
if (Director::is_cli()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = '';
|
||||
$line = '';
|
||||
|
||||
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
|
||||
// There's nothing we can do about this, because it's an operating system function!
|
||||
if (!headers_sent($file, $line)) {
|
||||
@session_regenerate_id(true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @inherit
|
||||
*/
|
||||
public function logOut(HTTPRequest $request)
|
||||
{
|
||||
// @todo couple the session to a request object
|
||||
// $session = $request->getSession();
|
||||
|
||||
Session::clear($this->getSessionVariable());
|
||||
}
|
||||
}
|
@ -428,6 +428,18 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
));
|
||||
}
|
||||
|
||||
private static $currentUser;
|
||||
|
||||
public static function setCurrentUser($currentUser)
|
||||
{
|
||||
self::$currentUser = $currentUser;
|
||||
}
|
||||
|
||||
public static function getCurrentUser()
|
||||
{
|
||||
return self::$currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login forms for all available authentication methods
|
||||
*
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
@ -13,6 +14,7 @@ use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\MemberAuthenticator\Authenticator;
|
||||
use SilverStripe\Security\MemberAuthenticator\LoginForm;
|
||||
use SilverStripe\Security\CMSMemberLoginForm;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
@ -103,7 +105,7 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
$this->assertEmpty($tempID);
|
||||
|
||||
// If the user logs in then they have a temp id
|
||||
$member->logIn(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($member, true, new HTTPRequest('GET', '/'));
|
||||
$tempID = $member->TempIDHash;
|
||||
$this->assertNotEmpty($tempID);
|
||||
|
||||
|
@ -15,10 +15,12 @@ use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\MemberPassword;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Security\PasswordEncryptor_Blowfish;
|
||||
use SilverStripe\Security\RememberLoginHash;
|
||||
use SilverStripe\Security\Member_Validator;
|
||||
use SilverStripe\Security\Tests\MemberTest\FieldsExtension;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
|
||||
class MemberTest extends FunctionalTest
|
||||
{
|
||||
@ -534,26 +536,25 @@ class MemberTest extends FunctionalTest
|
||||
$member = $this->objFromFixture(Member::class, 'test');
|
||||
$member2 = $this->objFromFixture(Member::class, 'staffmember');
|
||||
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
|
||||
/* Not logged in, you can't view, delete or edit the record */
|
||||
$this->assertFalse($member->canView());
|
||||
$this->assertFalse($member->canDelete());
|
||||
$this->assertFalse($member->canEdit());
|
||||
|
||||
/* Logged in users can edit their own record */
|
||||
$this->session()->inst_set('loggedInAs', $member->ID);
|
||||
$this->logInAs($member);
|
||||
$this->assertTrue($member->canView());
|
||||
$this->assertFalse($member->canDelete());
|
||||
$this->assertTrue($member->canEdit());
|
||||
|
||||
/* Other uses cannot view, delete or edit others records */
|
||||
$this->session()->inst_set('loggedInAs', $member2->ID);
|
||||
$this->logInAs($member2);
|
||||
$this->assertFalse($member->canView());
|
||||
$this->assertFalse($member->canDelete());
|
||||
$this->assertFalse($member->canEdit());
|
||||
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->addExtensions($extensions);
|
||||
$this->logOut();
|
||||
}
|
||||
|
||||
public function testAuthorisedMembersCanManipulateOthersRecords()
|
||||
@ -562,14 +563,19 @@ class MemberTest extends FunctionalTest
|
||||
$member2 = $this->objFromFixture(Member::class, 'staffmember');
|
||||
|
||||
/* Group members with SecurityAdmin permissions can manipulate other records */
|
||||
$this->session()->inst_set('loggedInAs', $member->ID);
|
||||
$this->logInAs($member);
|
||||
$this->assertTrue($member2->canView());
|
||||
$this->assertTrue($member2->canDelete());
|
||||
$this->assertTrue($member2->canEdit());
|
||||
|
||||
$this->addExtensions($extensions);
|
||||
$this->logOut();
|
||||
}
|
||||
|
||||
public function testExtendedCan()
|
||||
{
|
||||
|
||||
$extensions = $this->removeExtensions(Object::get_extensions(Member::class));
|
||||
$member = $this->objFromFixture(Member::class, 'test');
|
||||
|
||||
/* Normal behaviour is that you can't view a member unless canView() on an extension returns true */
|
||||
@ -664,12 +670,12 @@ class MemberTest extends FunctionalTest
|
||||
'Adding new admin group relation is not allowed for non-admin members'
|
||||
);
|
||||
|
||||
$this->session()->inst_set('loggedInAs', $adminMember->ID);
|
||||
$this->logInAs($adminMember);
|
||||
$this->assertTrue(
|
||||
$staffMember->onChangeGroups(array($newAdminGroup->ID)),
|
||||
'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
|
||||
);
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
|
||||
$this->assertTrue(
|
||||
$adminMember->onChangeGroups(array($newAdminGroup->ID)),
|
||||
@ -719,7 +725,7 @@ class MemberTest extends FunctionalTest
|
||||
);
|
||||
|
||||
// Test staff member can be added if they are already admin
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
$this->assertFalse($adminMember->inGroup($newAdminGroup));
|
||||
$adminMember->Groups()->add($newAdminGroup);
|
||||
$this->assertTrue(
|
||||
@ -872,7 +878,8 @@ class MemberTest extends FunctionalTest
|
||||
{
|
||||
$m1 = $this->objFromFixture(Member::class, 'grouplessmember');
|
||||
|
||||
$m1->login(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
|
||||
$hashes = RememberLoginHash::get()->filter('MemberID', $m1->ID);
|
||||
$this->assertEquals($hashes->count(), 1);
|
||||
$firstHash = $hashes->first();
|
||||
@ -887,7 +894,8 @@ class MemberTest extends FunctionalTest
|
||||
*/
|
||||
$m1 = $this->objFromFixture(Member::class, 'noexpiry');
|
||||
|
||||
$m1->logIn(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
|
||||
$firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->first();
|
||||
$this->assertNotNull($firstHash);
|
||||
|
||||
@ -914,7 +922,7 @@ class MemberTest extends FunctionalTest
|
||||
);
|
||||
$this->assertContains($message, $response->getBody());
|
||||
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
|
||||
// A wrong token or a wrong device ID should not let us autologin
|
||||
$response = $this->get(
|
||||
@ -922,7 +930,7 @@ class MemberTest extends FunctionalTest
|
||||
$this->session(),
|
||||
null,
|
||||
array(
|
||||
'alc_enc' => $m1->ID.':'.str_rot13($token),
|
||||
'alc_enc' => $m1->ID.':asdfasd'.str_rot13($token),
|
||||
'alc_device' => $firstHash->DeviceID
|
||||
)
|
||||
);
|
||||
@ -965,7 +973,7 @@ class MemberTest extends FunctionalTest
|
||||
* @var Member $m1
|
||||
*/
|
||||
$m1 = $this->objFromFixture(Member::class, 'noexpiry');
|
||||
$m1->logIn(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
$firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->first();
|
||||
$this->assertNotNull($firstHash);
|
||||
|
||||
@ -995,7 +1003,7 @@ class MemberTest extends FunctionalTest
|
||||
);
|
||||
$this->assertContains($message, $response->getBody());
|
||||
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
|
||||
// re-generates the hash so we can get the token
|
||||
$firstHash->Hash = $firstHash->getNewHash($m1);
|
||||
@ -1015,7 +1023,7 @@ class MemberTest extends FunctionalTest
|
||||
)
|
||||
);
|
||||
$this->assertNotContains($message, $response->getBody());
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
DBDatetime::clear_mock_now();
|
||||
}
|
||||
|
||||
@ -1024,10 +1032,10 @@ class MemberTest extends FunctionalTest
|
||||
$m1 = $this->objFromFixture(Member::class, 'noexpiry');
|
||||
|
||||
// First device
|
||||
$m1->login(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
Cookie::set('alc_device', null);
|
||||
// Second device
|
||||
$m1->login(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
|
||||
// Hash of first device
|
||||
$firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->first();
|
||||
@ -1068,7 +1076,7 @@ class MemberTest extends FunctionalTest
|
||||
);
|
||||
$this->assertContains($message, $response->getBody());
|
||||
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
|
||||
// Accessing the login page from the second device
|
||||
$response = $this->get(
|
||||
@ -1100,7 +1108,7 @@ class MemberTest extends FunctionalTest
|
||||
|
||||
// Logging out from any device when all login hashes should be removed
|
||||
RememberLoginHash::config()->update('logout_across_devices', true);
|
||||
$m1->login(true);
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($m1, true, new HTTPRequest('GET', '/'));
|
||||
$response = $this->get('Security/logout', $this->session());
|
||||
$this->assertEquals(
|
||||
RememberLoginHash::get()->filter('MemberID', $m1->ID)->count(),
|
||||
|
@ -378,6 +378,8 @@ class SecurityTest extends FunctionalTest
|
||||
);
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
|
||||
$this->logOut();
|
||||
|
||||
/* EXPIRED PASSWORDS ARE SENT TO THE CHANGE PASSWORD FORM */
|
||||
$expiredResponse = $this->doTestLoginForm('expired@silverstripe.com', '1nitialPassword');
|
||||
$this->assertEquals(302, $expiredResponse->getStatusCode());
|
||||
@ -415,6 +417,7 @@ class SecurityTest extends FunctionalTest
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
|
||||
// Check if we can login with the new password
|
||||
$this->logOut();
|
||||
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword');
|
||||
$this->assertEquals(302, $goodResponse->getStatusCode());
|
||||
$this->assertEquals(
|
||||
@ -460,6 +463,7 @@ class SecurityTest extends FunctionalTest
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
|
||||
// Check if we can login with the new password
|
||||
$this->logOut();
|
||||
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword');
|
||||
$this->assertEquals(302, $goodResponse->getStatusCode());
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
@ -532,7 +536,7 @@ class SecurityTest extends FunctionalTest
|
||||
);
|
||||
|
||||
// Log the user out
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->logOut();
|
||||
|
||||
// Login again with wrong password, but less attempts than threshold
|
||||
for ($i = 1; $i < Member::config()->lock_out_after_incorrect_logins; $i++) {
|
||||
|
Loading…
Reference in New Issue
Block a user