API: Security.authenticators is now a map, not an array

Authenticators is now a map of keys -> service names. The key is used
in things such as URL segments. The “default_authenticator” value has
been replaced with the key “default” in this map, although in time a
default authenticator may not be needed.
IX: Refactor login() to avoid code duplication on single/multiple handlers
IX: Refactor LoginHandler to be more amenable to extension
IX: Fixed permissionFailure hack
his LoginHandler is expected to be the starting point for other
custom authenticators so it should be easier to repurpose components
`of it.
IX: Fix database-is-ready checks in tests.
IX: Fixed MemberAuthenticatorTest to match the new API
IX: Update security URLs in MemberTest
This commit is contained in:
Sam Minnee 2017-04-23 15:30:33 +12:00 committed by Simon Erkelens
parent e226b67d06
commit 7af7e6719e
10 changed files with 256 additions and 284 deletions

View File

@ -4,6 +4,5 @@ SilverStripe\Security\MemberAuthenticator\LoginForm:
- Password
SilverStripe\Security\Security:
default_authenticator: SilverStripe\Security\MemberAuthenticator\Authenticator
authenticators:
- SilverStripe\Security\MemberAuthenticator\Authenticator
default: SilverStripe\Security\MemberAuthenticator\Authenticator

View File

@ -276,7 +276,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
if (Controller::has_curr()) {
Controller::curr()->setSession(Session::create(array()));
}
Security::$database_is_ready = null;
Security::clear_database_is_ready();
// Set up test routes
$this->setUpRoutes();

View File

@ -180,7 +180,7 @@ PHP
public function LoginForm()
{
$authenticator = $this->getAuthenticator();
$authenticator = $this->getAuthenticator('default');
if ($authenticator && $authenticator::supports_cms()) {
return $authenticator::get_cms_login_form($this);
}

View File

@ -10,6 +10,7 @@ use InvalidArgumentException;
use SilverStripe\Security\Authenticator as BaseAuthenticator;
use SilverStripe\Security\Security;
use SilverStripe\Security\Member;
use SilverStripe\Security\LoginAttempt;
/**
* Authenticator for the default "member" method
@ -134,7 +135,7 @@ class Authenticator implements BaseAuthenticator
* @param array $data
* @param Member $member
*/
protected function recordLoginAttempt($data, $member)
protected function recordLoginAttempt($data, $member, $success)
{
if (!Security::config()->login_recording) {
return;

View File

@ -94,21 +94,28 @@ class LoginHandler extends RequestHandler
* @param LoginHandler $formHandler
* @return HTTPResponse
*/
public function doLogin($data, $formHandler)
public function doLogin($data, $form)
{
if ($this->performLogin($data)) {
return $this->logInUserAndRedirect($data, $formHandler);
$failureMessage = null;
// Successful login
if ($member = $this->checkLogin($data, $failureMessage)) {
$this->performLogin($member, $data);
return $this->redirectAfterSuccessfulLogin();
}
$form->sessionMessage($failureMessage, 'bad');
// Failed login
/** @skipUpgrade */
if (array_key_exists('Email', $data)) {
Session::set('SessionForms.MemberLoginForm.Email', $data['Email']);
Session::set('SessionForms.MemberLoginForm.Remember', isset($data['Remember']));
}
return $this->redirectBack();
// Fail to login redirects back to form
return $formHandler->redirectBackToForm();
return $form->getRequestHandler()->redirectBackToForm();
}
@ -132,7 +139,7 @@ class LoginHandler extends RequestHandler
* @param array $data
* @return HTTPResponse
*/
protected function logInUserAndRedirect($data, $formHandler)
protected function redirectAfterSuccessfulLogin()
{
Session::clear('SessionForms.MemberLoginForm.Email');
Session::clear('SessionForms.MemberLoginForm.Remember');
@ -156,13 +163,6 @@ class LoginHandler extends RequestHandler
// Redirect the user to the page where they came from
if ($member) {
if (!empty($data['Remember'])) {
Session::set('SessionForms.MemberLoginForm.Remember', '1');
$member->logIn(true);
} else {
$member->logIn();
}
// Welcome message
$message = _t(
'SilverStripe\\Security\\Member.WELCOMEBACK',
@ -188,7 +188,8 @@ class LoginHandler extends RequestHandler
*/
public function logout()
{
return Security::singleton()->logout();
Security::singleton()->logout();
return $this->redirectBack();
}
/**
@ -198,22 +199,33 @@ class LoginHandler extends RequestHandler
* @return Member Returns the member object on successful authentication
* or NULL on failure.
*/
public function performLogin($data)
public function checkLogin($data, &$message)
{
$message = null;
$member = $this->authenticator->authenticate($data, $message);
if ($member) {
$member->LogIn(isset($data['Remember']));
return $member;
} else {
Security::setLoginMessage($message, ValidationResult::TYPE_ERROR);
}
// No member, can't login
$this->extend('authenticationFailed', $data);
return null;
} else {
// No member, can't login
$this->extend('authenticationFailed', $data);
return null;
}
}
/**
* Try to authenticate the user
*
* @param array $data Submitted data
* @return Member Returns the member object on successful authentication
* or NULL on failure.
*/
public function performLogin($member, $data)
{
$member->LogIn(isset($data['Remember']));
return $member;
}
/**
* Invoked if password is expired and must be changed
*

View File

@ -220,10 +220,64 @@ class Security extends Controller implements TemplateGlobalProvider
*/
protected static $database_is_ready = false;
/**
* @var array available authenticators
*/
protected static $authenticators = [];
/**
* @var string Default authenticator
*/
protected static $default_authenticator = MemberAuthenticator\Authenticator::class;
/**
* @inheritdoc
*/
protected function init()
{
parent::init();
// Prevent clickjacking, see https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
$frameOptions = $this->config()->get('frame_options');
if ($frameOptions) {
$this->getResponse()->addHeader('X-Frame-Options', $frameOptions);
}
// Prevent search engines from indexing the login page
$robotsTag = $this->config()->get('robots_tag');
if ($robotsTag) {
$this->getResponse()->addHeader('X-Robots-Tag', $robotsTag);
}
}
/**
* @inheritdoc
*/
public function index()
{
return $this->httpError(404); // no-op
}
/**
* Get the selected authenticator for this request
*
* @param $name string The identifier of the authenticator in your config
* @return string Class name of Authenticator
* @throws LogicException
*/
protected function getAuthenticator($name)
{
$authenticators = self::config()->authenticators;
if (!$name) $name = 'default';
if (isset($authenticators[$name])) {
return Injector::inst()->get($authenticators[$name]);
}
throw new LogicException('No valid authenticator found');
}
/**
* Get all registered authenticators
*
@ -231,44 +285,24 @@ class Security extends Controller implements TemplateGlobalProvider
*/
public static function getAuthenticators()
{
$authenticatorClasses = self::config()->authenticators;
$default = self::config()->default_authenticator;
if (!$authenticatorClasses) {
if ($default) {
$authenticatorClasses = [$default];
} else {
return [];
}
}
// put default authenticator first (mainly for tab-order on loginform)
// But only if there's no other authenticator
if (($key = array_search($default, $authenticatorClasses, true)) && count($$authenticatorClasses) > 1) {
unset($authenticatorClasses[$key]);
array_unshift($authenticatorClasses, $default);
}
$authenticators = self::config()->authenticators;
return array_map(function ($class) {
return Injector::inst()->get($class);
}, $authenticatorClasses);
}, $authenticators);
}
/**
* Check if a given authenticator is registered
*
* @param string $authenticator Name of the authenticator class to check
* @param string $authenticator The configured identifier of the authenicator
* @return bool Returns TRUE if the authenticator is registered, FALSE
* otherwise.
*/
public static function hasAuthenticator($authenticator)
{
$authenticators = self::config()->get('authenticators');
if (count($authenticators) === 0) {
$authenticators = [self::config()->get('default_authenticator')];
}
return in_array($authenticator, $authenticators, true);
return !empty($authenticators[$authenticator]);
}
/**
@ -358,12 +392,8 @@ class Security extends Controller implements TemplateGlobalProvider
$message = $messageSet['default'];
}
// Somewhat hackish way to render a login form with an error message.
// $me = new Security();
// $form = $me->LoginForm();
// $form->sessionMessage($message, ValidationResult::TYPE_WARNING);
// Session::set('MemberLoginForm.force_message', 1);
$loginResponse = $me->login();
Security::setLoginMessage($message, ValidationResult::TYPE_WARNING);
$loginResponse = (new Security())->login(new HTTPRequest('GET', '/'));
if ($loginResponse instanceof HTTPResponse) {
return $loginResponse;
}
@ -391,50 +421,6 @@ class Security extends Controller implements TemplateGlobalProvider
));
}
protected function init()
{
parent::init();
// Prevent clickjacking, see https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
$frameOptions = $this->config()->get('frame_options');
if ($frameOptions) {
$this->getResponse()->addHeader('X-Frame-Options', $frameOptions);
}
// Prevent search engines from indexing the login page
$robotsTag = $this->config()->get('robots_tag');
if ($robotsTag) {
$this->getResponse()->addHeader('X-Robots-Tag', $robotsTag);
}
}
public function index()
{
return $this->httpError(404); // no-op
}
/**
* Get the selected authenticator for this request
*
* @return string Class name of Authenticator
* @throws LogicException
*/
protected function getAuthenticator()
{
$authenticator = $this->getRequest()->requestVar('AuthenticationMethod');
if ($authenticator && self::hasAuthenticator($authenticator)) {
return Injector::inst()->get($authenticator);
} elseif ($authenticator !== '') {
$authenticators = self::getAuthenticators();
if (count($authenticators) > 0) {
return $authenticators[0];
}
}
throw new LogicException('No valid authenticator found');
}
/**
* Get the login form to process according to the submitted data
*
@ -443,7 +429,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/
public function LoginForm()
{
$authenticator = $this->getAuthenticator();
$authenticator = $this->getAuthenticator('default');
if ($authenticator) {
$handler = $authenticator->getLoginHandler($this->Link());
return $handler->handleRequest($this->request, DataModel::inst());
@ -654,7 +640,9 @@ class Security extends Controller implements TemplateGlobalProvider
* For multiple authenticators, Security_MultiAuthenticatorLogin is used.
* See getTemplatesFor and getIncludeTemplate for how to override template logic
*
* @return string|HTTPResponse Returns the "login" page as HTML code.
* @param $request
* @return HTTPResponse|string Returns the "login" page as HTML code.
* @throws HTTPResponse_Exception
*/
public function login($request)
{
@ -666,61 +654,54 @@ class Security extends Controller implements TemplateGlobalProvider
$link = $this->link("login");
// Delegate to a single handler - Security/login/<authname>/...
if ($authenticatorName = $request->param('ID')) {
if ($name = $request->param('ID')) {
$request->shift();
$authenticator = $this->getAuthenticator($authenticatorName);
$authenticator = $this->getAuthenticator($name);
if (!$authenticator) {
throw new HTTPResponse_Exception(404, 'No authenticator "' . $authenticatorName . '"');
throw new HTTPResponse_Exception(404, 'No authenticator "' . $name . '"');
}
$handler = $authenticator->getLoginHandler(Controller::join_links($link, $authenticatorName));
return $this->delegateToHandler(
$handler,
_t('Security.LOGIN', 'Log in'),
$this->getTemplatesFor('login')
);
$authenticators = [ $name => $authenticator ];
// Delegate to all of them, building a tabbed view - Security/login
} else {
$handlers = $this->getAuthenticators();
array_walk(
$handlers,
function (&$auth, $name) use ($link) {
$auth = $auth->getLoginHandler(Controller::join_links($link, $name));
}
);
if (count($handlers) === 1) {
return $this->delegateToHandler(
array_values($handlers)[0],
_t('Security.LOGIN', 'Log in'),
$this->getTemplatesFor('login')
);
} else {
return $this->delegateToFormSet(
$handlers,
_t('Security.LOGIN', 'Log in'),
$this->getTemplatesFor('login')
);
}
$authenticators = $this->getAuthenticators();
}
$handlers = $authenticators;
array_walk(
$handlers,
function (&$auth, $name) use ($link) {
$auth = $auth->getLoginHandler(Controller::join_links($link, $name));
}
);
return $this->delegateToMultipleHandlers(
$handlers,
_t('Security.LOGIN', 'Log in'),
$this->getTemplatesFor('login')
);
}
/**
* Delegate to an number of handlers, extracting their forms and rendering a tabbed form-set.
* This is used to built the log-in page where there are multiple authenticators active.
*
* If a single handler is passed, delegateToHandler() will be called instead
*
* @param string $title The title of the form
* @param array $templates
* @return array|HTTPResponse|RequestHandler|\SilverStripe\ORM\FieldType\DBHTMLText|string
*/
protected function delegateToFormSet(array $handlers, $title, array $templates)
protected function delegateToMultipleHandlers(array $handlers, $title, array $templates)
{
// Simpler case for a single authenticator
if (count($handlers) === 1) {
return $this->delegateToHandler(array_values($handlers)[0], $title, $templates);
}
// Process each of the handlers
$results = array_map(
function ($handler) {
@ -778,9 +759,10 @@ class Security extends Controller implements TemplateGlobalProvider
/**
* Render the given fragments into a security page controller with the given title.
* @param $title string The title to give the security page
* @param $fragments A map of objects to render into the page, e.g. "Form"
* @param $templates An array of templates to use for the render
* @param string $title string The title to give the security page
* @param array $fragments A map of objects to render into the page, e.g. "Form"
* @param array $templates An array of templates to use for the render
* @return HTTPResponse|\SilverStripe\ORM\FieldType\DBHTMLText
*/
protected function renderWrappedController($title, array $fragments, array $templates)
{
@ -804,7 +786,7 @@ class Security extends Controller implements TemplateGlobalProvider
'Message' => DBField::create_field('HTMLFragment', $message),
'MessageType' => $messageType
];
$result = array_merge($fragments, $messageResult);
$fragments = array_merge($fragments, $messageResult);
}
return $controller->customise($fragments)->renderWith($templates);
@ -823,7 +805,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/
public function lostpassword()
{
$handler = $this->getAuthenticator()->getLostPasswordHandler(
$handler = $this->getAuthenticator('default')->getLostPasswordHandler(
Controller::join_links($this->link(), 'lostpassword')
);
@ -834,26 +816,6 @@ class Security extends Controller implements TemplateGlobalProvider
);
}
/**
* Create a link to the password reset form.
*
* GET parameters used:
* - m: member ID
* - t: plaintext token
*
* @param Member $member Member object associated with this link.
* @param string $autologinToken The auto login token.
* @return string
*/
public static function getPasswordResetLink($member, $autologinToken)
{
$autologinToken = urldecode($autologinToken);
$selfControllerClass = __CLASS__;
/** @var static $selfController */
$selfController = new $selfControllerClass();
return $selfController->Link('changepassword') . "?m={$member->ID}&t=$autologinToken";
}
/**
* Show the "change password" page.
* This page can either be called directly by logged-in users
@ -941,6 +903,26 @@ class Security extends Controller implements TemplateGlobalProvider
return $customisedController->renderWith($this->getTemplatesFor('changepassword'));
}
/**
* Create a link to the password reset form.
*
* GET parameters used:
* - m: member ID
* - t: plaintext token
*
* @param Member $member Member object associated with this link.
* @param string $autologinToken The auto login token.
* @return string
*/
public static function getPasswordResetLink($member, $autologinToken)
{
$autologinToken = urldecode($autologinToken);
$selfControllerClass = __CLASS__;
/** @var static $selfController */
$selfController = new $selfControllerClass();
return $selfController->Link('changepassword') . "?m={$member->ID}&t=$autologinToken";
}
/**
* Factory method for the lost password form
*
@ -1219,6 +1201,46 @@ class Security extends Controller implements TemplateGlobalProvider
return true;
}
/**
* Resets the database_is_ready cache
*/
public static function clear_database_is_ready()
{
self::$database_is_ready = null;
self::$force_database_is_ready = null;
}
/**
* For the database_is_ready call to return a certain value - used for testing
*/
public static function force_database_is_ready($isReady)
{
self::$force_database_is_ready = $isReady;
}
/**
* Enable or disable recording of login attempts
* through the {@link LoginRecord} object.
*
* @deprecated 4.0 Use the "Security.login_recording" config setting instead
* @param boolean $bool
*/
public static function set_login_recording($bool)
{
Deprecation::notice('4.0', 'Use the "Security.login_recording" config setting instead');
self::$login_recording = (bool)$bool;
}
/**
* @deprecated 4.0 Use the "Security.login_recording" config setting instead
* @return boolean
*/
public static function login_recording()
{
Deprecation::notice('4.0', 'Use the "Security.login_recording" config setting instead');
return self::$login_recording;
}
/**
* @config
* @var string Set the default login dest

View File

@ -30,7 +30,7 @@ class BasicAuthTest extends FunctionalTest
// Fixtures assume Email is the field used to identify the log in identity
Member::config()->unique_identifier_field = 'Email';
Security::$force_database_is_ready = true; // Prevents Member test subclasses breaking ready test
Security::force_database_is_ready(true); // Prevents Member test subclasses breaking ready test
Member::config()->lock_out_after_incorrect_logins = 10;
}

View File

@ -9,13 +9,14 @@ use SilverStripe\Security\PasswordEncryptor;
use SilverStripe\Security\PasswordEncryptor_PHPHash;
use SilverStripe\Security\Security;
use SilverStripe\Security\Member;
use SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Security\MemberLoginForm;
use SilverStripe\Security\MemberAuthenticator\Authenticator;
use SilverStripe\Security\MemberAuthenticator\LoginForm;
use SilverStripe\Security\CMSMemberLoginForm;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Control\HTTPRequest;
class MemberAuthenticatorTest extends SapphireTest
{
@ -41,59 +42,6 @@ class MemberAuthenticatorTest extends SapphireTest
parent::tearDown();
}
public function testLegacyPasswordHashMigrationUponLogin()
{
$member = new Member();
$field=Member::config()->unique_identifier_field;
$member->$field = 'test1@test.com';
$member->PasswordEncryption = "sha1";
$member->Password = "mypassword";
$member->write();
$data = array(
'Email' => $member->$field,
'Password' => 'mypassword'
);
MemberAuthenticator::authenticate($data);
/**
* @var Member $member
*/
$member = DataObject::get_by_id(Member::class, $member->ID);
$this->assertEquals($member->PasswordEncryption, "sha1_v2.4");
$result = $member->checkPassword('mypassword');
$this->assertTrue($result->isValid());
}
public function testNoLegacyPasswordHashMigrationOnIncompatibleAlgorithm()
{
Config::inst()->update(
PasswordEncryptor::class,
'encryptors',
array('crc32' => array(PasswordEncryptor_PHPHash::class => 'crc32'))
);
$field=Member::config()->unique_identifier_field;
$member = new Member();
$member->$field = 'test2@test.com';
$member->PasswordEncryption = "crc32";
$member->Password = "mypassword";
$member->write();
$data = array(
'Email' => $member->$field,
'Password' => 'mypassword'
);
MemberAuthenticator::authenticate($data);
$member = DataObject::get_by_id(Member::class, $member->ID);
$this->assertEquals($member->PasswordEncryption, "crc32");
$result = $member->checkPassword('mypassword');
$this->assertTrue($result->isValid());
}
public function testCustomIdentifierField()
{
@ -109,36 +57,46 @@ class MemberAuthenticatorTest extends SapphireTest
public function testGenerateLoginForm()
{
$authenticator = new Authenticator();
$controller = new Security();
// Create basic login form
$frontendForm = MemberAuthenticator::get_login_form($controller);
$this->assertTrue($frontendForm instanceof MemberLoginForm);
$frontendResponse = $authenticator
->getLoginHandler($controller->link())
->handleRequest(new HTTPRequest('get', '/'), \SilverStripe\ORM\DataModel::inst());
$this->assertTrue(is_array($frontendResponse));
$this->assertTrue(isset($frontendResponse['Form']));
$this->assertTrue($frontendResponse['Form'] instanceof LoginForm);
}
/* TO DO - reenable
public function testGenerateCMSLoginForm()
{
$authenticator = new Authenticator();
// Supports cms login form
$this->assertTrue(MemberAuthenticator::supports_cms());
$cmsForm = MemberAuthenticator::get_cms_login_form($controller);
$this->assertTrue($cmsForm instanceof CMSMemberLoginForm);
}
*/
/**
* Test that a member can be authenticated via their temp id
*/
public function testAuthenticateByTempID()
{
$authenticator = new Authenticator();
$member = new Member();
$member->Email = 'test1@test.com';
$member->PasswordEncryption = "sha1";
$member->Password = "mypassword";
$member->write();
// Make form
$controller = new Security();
/**
* @skipUpgrade
*/
$form = new Form($controller, 'Form', new FieldList(), new FieldList());
// If the user has never logged in, then the tempid should be empty
$tempID = $member->TempIDHash;
$this->assertEmpty($tempID);
@ -149,35 +107,32 @@ class MemberAuthenticatorTest extends SapphireTest
$this->assertNotEmpty($tempID);
// Test correct login
$result = MemberAuthenticator::authenticate(
$result = $authenticator->authenticate(
array(
'tempid' => $tempID,
'Password' => 'mypassword'
),
$form
$message
);
$form->restoreFormState();
$this->assertNotEmpty($result);
$this->assertEquals($result->ID, $member->ID);
$this->assertEmpty($form->getMessage());
$this->assertEmpty($message);
// Test incorrect login
$form->clearMessage();
$result = MemberAuthenticator::authenticate(
$result = $authenticator->authenticate(
array(
'tempid' => $tempID,
'Password' => 'notmypassword'
),
$form
$message
);
$form->restoreFormState();
$this->assertEmpty($result);
$this->assertEquals(
_t('SilverStripe\\Security\\Member.ERRORWRONGCRED', 'The provided details don\'t seem to be correct. Please try again.'),
$form->getMessage()
$message
);
$this->assertEquals(ValidationResult::TYPE_ERROR, $form->getMessageType());
$this->assertEquals(ValidationResult::CAST_TEXT, $form->getMessageCast());
}
/**
@ -185,61 +140,50 @@ class MemberAuthenticatorTest extends SapphireTest
*/
public function testDefaultAdmin()
{
// Make form
$controller = new Security();
/**
* @skipUpgrade
*/
$form = new Form($controller, 'Form', new FieldList(), new FieldList());
$authenticator = new Authenticator();
// Test correct login
$result = MemberAuthenticator::authenticate(
$result = $authenticator->authenticate(
array(
'Email' => 'admin',
'Password' => 'password'
),
$form
$message
);
$form->restoreFormState();
$this->assertNotEmpty($result);
$this->assertEquals($result->Email, Security::default_admin_username());
$this->assertEmpty($form->getMessage());
$this->assertEmpty($message);
// Test incorrect login
$form->clearMessage();
$result = MemberAuthenticator::authenticate(
$result = $authenticator->authenticate(
array(
'Email' => 'admin',
'Password' => 'notmypassword'
),
$form
$message
);
$form->restoreFormState();
$this->assertEmpty($result);
$this->assertEquals(
'The provided details don\'t seem to be correct. Please try again.',
$form->getMessage()
$message
);
$this->assertEquals(ValidationResult::TYPE_ERROR, $form->getMessageType());
$this->assertEquals(ValidationResult::CAST_TEXT, $form->getMessageCast());
}
public function testDefaultAdminLockOut()
{
$authenticator = new Authenticator();
Config::inst()->update(Member::class, 'lock_out_after_incorrect_logins', 1);
Config::inst()->update(Member::class, 'lock_out_delay_mins', 10);
DBDatetime::set_mock_now('2016-04-18 00:00:00');
$controller = new Security();
/** @skipUpgrade */
$form = new Form($controller, 'Form', new FieldList(), new FieldList());
// Test correct login
MemberAuthenticator::authenticate(
$authenticator->authenticate(
[
'Email' => 'admin',
'Password' => 'wrongpassword'
],
$form
$dummy
);
$this->assertTrue(Member::default_admin()->isLockedOut());

View File

@ -237,13 +237,13 @@ class MemberTest extends FunctionalTest
$this->assertNotNull($member);
// Initiate a password-reset
$response = $this->post('Security/LostPasswordForm', array('Email' => $member->Email));
$response = $this->post('Security/lostpassword/LostPasswordForm', array('Email' => $member->Email));
$this->assertEquals($response->getStatusCode(), 302);
// We should get redirected to Security/passwordsent
$this->assertContains(
'Security/passwordsent/testuser@example.com',
'Security/lostpassword/passwordsent/testuser@example.com',
urldecode($response->getHeader('Location'))
);
@ -942,12 +942,11 @@ class MemberTest extends FunctionalTest
// Re-logging (ie 'alc_enc' has expired), and not checking the "Remember Me" option
// should remove all previous hashes for this device
$response = $this->post(
'Security/LoginForm',
'Security/login/default/LoginForm',
array(
'Email' => $m1->Email,
'Password' => '1nitialPassword',
'AuthenticationMethod' => MemberAuthenticator::class,
'action_dologin' => 'action_dologin'
'action_doLogin' => 'action_doLogin'
),
null,
$this->session(),

View File

@ -187,14 +187,14 @@ class SecurityTest extends FunctionalTest
}
$response = $this->getRecursive('SecurityTest_SecuredController');
$this->assertContains(Convert::raw2xml("That page is secured."), $response->getBody());
$this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
$this->assertContains('<input type="submit" name="action_doLogin"', $response->getBody());
// Non-logged in user should not be redirected, but instead shown the login form
// No message/context is available as the user has not attempted to view the secured controller
$response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
$this->assertNotContains(Convert::raw2xml("That page is secured."), $response->getBody());
$this->assertNotContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
$this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
$this->assertContains('<input type="submit" name="action_doLogin"', $response->getBody());
// BackURL with permission error (wrong permissions) should not redirect
$this->logInAs('grouplessmember');
@ -233,7 +233,7 @@ class SecurityTest extends FunctionalTest
/* View the Security/login page */
$response = $this->get(Config::inst()->get(Security::class, 'login_url'));
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.action');
$items = $this->cssParser()->getBySelector('#LoginForm_LoginForm input.action');
/* We have only 1 input, one to allow the user to log in as someone else */
$this->assertEquals(count($items), 1, 'There is 1 input, allowing the user to log in as someone else.');
@ -242,11 +242,10 @@ class SecurityTest extends FunctionalTest
/* Submit the form, using only the logout action and a hidden field for the authenticator */
$response = $this->submitForm(
'MemberLoginForm_LoginForm',
'LoginForm_LoginForm',
null,
array(
'AuthenticationMethod' => MemberAuthenticator::class,
'action_dologout' => 1,
'action_logout' => 1,
)
);
@ -268,7 +267,7 @@ class SecurityTest extends FunctionalTest
/* Attempt to get into the admin section */
$response = $this->get(Config::inst()->get(Security::class, 'login_url'));
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.text');
$items = $this->cssParser()->getBySelector('#LoginForm_LoginForm input.text');
/* We have 2 text inputs - one for email, and another for the password */
$this->assertEquals(count($items), 2, 'There are 2 inputs - one for email, another for password');
@ -287,11 +286,11 @@ class SecurityTest extends FunctionalTest
$this->get(Config::inst()->get(Security::class, 'login_url'));
$items = $this
->cssParser()
->getBySelector('#MemberLoginForm_LoginForm #MemberLoginForm_LoginForm_Email');
->getBySelector('#LoginForm_LoginForm #LoginForm_LoginForm_Email');
$this->assertEquals(1, count($items));
$this->assertEmpty((string)$items[0]->attributes()->value);
$this->assertEquals('off', (string)$items[0]->attributes()->autocomplete);
$form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
$form = $this->cssParser()->getBySelector('#LoginForm_LoginForm');
$this->assertEquals(1, count($form));
$this->assertEquals('off', (string)$form[0]->attributes()->autocomplete);
@ -301,11 +300,11 @@ class SecurityTest extends FunctionalTest
$this->get(Config::inst()->get(Security::class, 'login_url'));
$items = $this
->cssParser()
->getBySelector('#MemberLoginForm_LoginForm #MemberLoginForm_LoginForm_Email');
->getBySelector('#LoginForm_LoginForm #LoginForm_LoginForm_Email');
$this->assertEquals(1, count($items));
$this->assertEquals('myuser@silverstripe.com', (string)$items[0]->attributes()->value);
$this->assertNotEquals('off', (string)$items[0]->attributes()->autocomplete);
$form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
$form = $this->cssParser()->getBySelector('#LoginForm_LoginForm');
$this->assertEquals(1, count($form));
$this->assertNotEquals('off', (string)$form[0]->attributes()->autocomplete);
}
@ -436,7 +435,7 @@ class SecurityTest extends FunctionalTest
// Request new password by email
$response = $this->get('Security/lostpassword');
$response = $this->post('Security/LostPasswordForm', array('Email' => 'testuser@example.com'));
$response = $this->post('Security/lostpassword/LostPasswordForm', array('Email' => 'testuser@example.com'));
$this->assertEmailSent('testuser@example.com');
@ -648,9 +647,7 @@ class SecurityTest extends FunctionalTest
public function testDatabaseIsReadyWithInsufficientMemberColumns()
{
$old = Security::$force_database_is_ready;
Security::$force_database_is_ready = null;
Security::$database_is_ready = false;
Security::clear_database_is_ready();
DBClassName::clear_classname_cache();
// Assumption: The database has been built correctly by the test runner,
@ -666,8 +663,6 @@ class SecurityTest extends FunctionalTest
// Rebuild the database (which re-adds the Email column), and try again
static::resetDBSchema(true);
$this->assertTrue(Security::database_is_ready());
Security::$force_database_is_ready = $old;
}
public function testSecurityControllerSendsRobotsTagHeader()
@ -697,13 +692,13 @@ class SecurityTest extends FunctionalTest
$this->get(Config::inst()->get(Security::class, 'login_url'));
return $this->submitForm(
"MemberLoginForm_LoginForm",
"LoginForm_LoginForm",
null,
array(
'Email' => $email,
'Password' => $password,
'AuthenticationMethod' => MemberAuthenticator::class,
'action_dologin' => 1,
'action_doLogin' => 1,
)
);
}
@ -751,7 +746,7 @@ class SecurityTest extends FunctionalTest
*/
protected function getValidationResult()
{
$result = $this->session()->inst_get('FormInfo.MemberLoginForm_LoginForm.result');
$result = $this->session()->inst_get('FormInfo.LoginForm_LoginForm.result');
if ($result) {
return unserialize($result);
}