ENHANCEMENT Update style of CMSLogin form

This commit is contained in:
Damian Mooyman 2017-05-31 17:48:16 +12:00
parent 22e084f288
commit 0f90c5b63f
13 changed files with 282 additions and 84 deletions

View File

@ -13,7 +13,7 @@ trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,js,json,css,scss,eslintrc}]
[*.{yml,js,json,css,scss,eslintrc,feature}]
indent_size = 2
indent_style = space

View File

@ -207,19 +207,19 @@ en:
ENTERINFO: 'Please enter a username and password.'
ERRORNOTADMIN: 'That user is not an administrator.'
ERRORNOTREC: 'That username / password isn''t recognised'
SilverStripe\Security\CMSMemberLoginForm:
SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm:
AUTHENTICATORNAME: 'CMS Member Login Form'
BUTTONFORGOTPASSWORD: 'Forgot password?'
BUTTONLOGIN: 'Log back in'
BUTTONFORGOTPASSWORD: 'Forgot password'
BUTTONLOGIN: 'Let me back in'
BUTTONLOGOUT: 'Log out'
PASSWORDEXPIRED: '<p>Your password has expired. <a target="_top" href="{link}">Please choose a new one.</a></p>'
SilverStripe\Security\CMSSecurity:
INVALIDUSER: '<p>Invalid user. <a target="_top" href="{link}">Please re-authenticate here</a> to continue.</p>'
LoginMessage: '<p>If you have any unsaved work you can return to where you left off by logging back in below.</p>'
LOGIN_MESSAGE: '<p>Your session has timed out due to inactivity.</p>'
SUCCESS: Success
SUCCESSCONTENT: '<p>Login success. If you are not automatically redirected <a target="_top" href="{link}">click here</a></p>'
TimedOutTitleAnonymous: 'Your session has timed out.'
TimedOutTitleMember: 'Hey {name}!<br />Your session has timed out.'
LOGIN_TITLE: 'Return to where you left off by logging back in'
SUCCESS_TITLE: Success
SilverStripe\Security\Group:
AddRole: 'Add a role for this group'
Code: 'Group Code'
@ -285,7 +285,7 @@ en:
PLURALS:
one: 'A Member'
other: '{count} Members'
REMEMBERME: 'Remember me next time? (for %d days on this device)'
REMEMBERME: 'Remember me next time? (for {count} days on this device)'
SINGULARNAME: Member
SUBJECTPASSWORDCHANGED: 'Your password has been changed'
SUBJECTPASSWORDRESET: 'Your password reset link'

View File

@ -234,7 +234,7 @@ fi:
PLURALS:
one: 'Käyttäjä'
other: '{count} Käyttäjää'
REMEMBERME: 'Muista minut? (%d päivän ajan tällä koneella)'
REMEMBERME: 'Muista minut? ({count} päivän ajan tällä koneella)'
SINGULARNAME: Käyttäjä
SUBJECTPASSWORDCHANGED: 'Salasanasi on vaihdettu'
SUBJECTPASSWORDRESET: 'Salasanasi palautuslinkki'

View File

@ -5,22 +5,21 @@ namespace SilverStripe\Security;
use SilverStripe\Admin\AdminRootController;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\Session;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\View\Requirements;
use SilverStripe\View\SSViewer;
/**
* Provides a security interface functionality within the cms
*/
class CMSSecurity extends Security
{
private static $casting = array(
'Title' => 'HTMLFragment'
);
private static $allowed_actions = array(
'login',
'LoginForm',
@ -39,7 +38,13 @@ class CMSSecurity extends Security
{
parent::init();
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/vendor.js');
// Assign default cms theme and replace user-specified themes
SSViewer::set_themes(LeftAndMain::config()->uninherited('admin_themes'));
// Core styles / vendor scripts
$admin = ModuleLoader::getModule('silverstripe/admin');
Requirements::javascript($admin->getResourcePath('client/dist/js/vendor.js'));
Requirements::css($admin->getResourcePath('client/dist/styles/bundle.css'));
}
public function login($request = null, $service = Authenticator::CMS_LOGIN)
@ -70,7 +75,8 @@ class CMSSecurity extends Security
*/
public function getTargetMember()
{
if ($tempid = $this->getRequest()->requestVar('tempid')) {
$tempid = $this->getRequest()->requestVar('tempid');
if ($tempid) {
return Member::member_from_tempid($tempid);
}
@ -85,36 +91,26 @@ class CMSSecurity extends Security
protected function getLoginMessage(&$messageType = null)
{
return parent::getLoginMessage($messageType)
?: _t(
'SilverStripe\\Security\\CMSSecurity.LoginMessage',
'<p>If you have any unsaved work you can return to where you left off by logging back in below.</p>'
);
$message = parent::getLoginMessage($messageType);
if ($message) {
return $message;
}
// Format
return _t(
__CLASS__.'.LOGIN_MESSAGE',
'<p>Your session has timed out due to inactivity</p>'
);
}
public function getTitle()
/**
* Check if there is a logged in member
*
* @return bool
*/
public function getIsloggedIn()
{
// Check if logged in already
if (Security::getCurrentUser()) {
return _t('SilverStripe\\Security\\CMSSecurity.SUCCESS', 'Success');
}
// Display logged-out message
$member = $this->getTargetMember();
if ($member) {
return _t(
'SilverStripe\\Security\\CMSSecurity.TimedOutTitleMember',
'Hey {name}!<br />Your session has timed out.',
'Title for CMS popup login form for a known user',
array('name' => $member->FirstName)
);
} else {
return _t(
'SilverStripe\\Security\\CMSSecurity.TimedOutTitleAnonymous',
'Your session has timed out.',
'Title for CMS popup login form without a known user'
);
}
return !!Security::getCurrentUser();
}
/**
@ -128,7 +124,7 @@ class CMSSecurity extends Security
$loginURLATT = Convert::raw2att($loginURL);
$loginURLJS = Convert::raw2js($loginURL);
$message = _t(
'SilverStripe\\Security\\CMSSecurity.INVALIDUSER',
__CLASS__.'.INVALIDUSER',
'<p>Invalid user. <a target="_top" href="{link}">Please re-authenticate here</a> to continue.</p>',
'Message displayed to user if their session cannot be restored',
array('link' => $loginURLATT)
@ -188,7 +184,7 @@ PHP
}
// Get redirect url
$controller = $this->getResponseController(_t('SilverStripe\\Security\\CMSSecurity.SUCCESS', 'Success'));
$controller = $this->getResponseController(_t(__CLASS__.'.SUCCESS', 'Success'));
$backURLs = array(
$this->getRequest()->requestVar('BackURL'),
Session::get('BackURL'),
@ -203,13 +199,13 @@ PHP
// Show login
$controller = $controller->customise(array(
'Content' => _t(
'SilverStripe\\Security\\CMSSecurity.SUCCESSCONTENT',
'<p>Login success. If you are not automatically redirected ' .
'Content' => DBField::create_field(DBHTMLText::class, _t(
__CLASS__.'.SUCCESSCONTENT',
'<p>Login success. If you are not automatically redirected '.
'<a target="_top" href="{link}">click here</a></p>',
'Login message displayed in the cms popup once a user has re-authenticated themselves',
array('link' => Convert::raw2att($backURL))
)
))
));
return $controller->renderWith($this->getTemplatesFor('success'));

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
use SilverStripe\Security\CMSSecurity;
@ -28,10 +29,20 @@ class CMSLoginHandler extends LoginHandler
public function redirectBackToForm()
{
// Redirect back to form
$url = $this->addBackURLParam(CMSSecurity::singleton()->Link('login'));
$url = $this->addBackURLParam($this->getReturnReferer());
return $this->redirect($url);
}
public function getReturnReferer()
{
// Try to retain referer (includes tempid param)
$referer = $this->getReferer();
if ($referer && Director::is_site_url($referer)) {
return $referer;
}
return CMSSecurity::singleton()->Link('login');
}
/**
* Redirect the user to the change password form.
*

View File

@ -4,12 +4,14 @@ namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Control\Controller;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\PasswordField;
use SilverStripe\Security\RememberLoginHash;
use SilverStripe\Security\Security;
/**
@ -35,6 +37,8 @@ class CMSMemberLoginForm extends MemberLoginForm
$actions = $this->getFormActions();
parent::__construct($controller, $authenticatorClass, $name, $fields, $actions);
$this->addExtraClass('form--no-dividers');
}
/**
@ -46,22 +50,24 @@ class CMSMemberLoginForm extends MemberLoginForm
$fields = FieldList::create([
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this),
HiddenField::create('tempid', null, $this->controller->getRequest()->requestVar('tempid')),
PasswordField::create("Password", _t('SilverStripe\\Security\\Member.PASSWORD', 'Password')),
LiteralField::create(
'forgotPassword',
sprintf(
'<p id="ForgotPassword"><a href="%s" target="_top">%s</a></p>',
$this->getExternalLink('lostpassword'),
_t('SilverStripe\\Security\\CMSMemberLoginForm.BUTTONFORGOTPASSWORD', "Forgot password?")
)
)
PasswordField::create("Password", _t('SilverStripe\\Security\\Member.PASSWORD', 'Password'))
]);
if (Security::config()->get('autologin_enabled')) {
$fields->push(CheckboxField::create(
"Remember",
_t('SilverStripe\\Security\\Member.REMEMBERME', "Remember me next time?")
));
$fields->insertAfter(
'Password',
CheckboxField::create(
"Remember",
_t('SilverStripe\\Security\\Member.KEEPMESIGNEDIN', "Keep me signed in")
)->setAttribute(
'title',
_t(
'SilverStripe\\Security\\Member.REMEMBERME',
"Remember me next time? (for {count} days on this device)",
[ 'count' => RememberLoginHash::config()->uninherited('token_expiry_days') ]
)
)
);
}
return $fields;
@ -72,7 +78,6 @@ class CMSMemberLoginForm extends MemberLoginForm
*/
public function getFormActions()
{
// Determine returnurl to redirect to parent page
$logoutLink = $this->getExternalLink('logout');
if ($returnURL = $this->controller->getRequest()->requestVar('BackURL')) {
@ -81,13 +86,22 @@ class CMSMemberLoginForm extends MemberLoginForm
// Make actions
$actions = FieldList::create([
FormAction::create('doLogin', _t('SilverStripe\\Security\\CMSMemberLoginForm.BUTTONLOGIN', "Log back in")),
FormAction::create('doLogin', _t(__CLASS__.'.BUTTONLOGIN', "Let me back in"))
->addExtraClass('btn-primary'),
LiteralField::create(
'doLogout',
sprintf(
'<p id="doLogout"><a href="%s" target="_top">%s</a></p>',
$logoutLink,
_t('SilverStripe\\Security\\CMSMemberLoginForm.BUTTONLOGOUT', "Log out")
'<a class="btn btn-secondary" href="%s" target="_top">%s</a>',
Convert::raw2att($logoutLink),
_t(__CLASS__.'.BUTTONLOGOUT', "Log out")
)
),
LiteralField::create(
'forgotPassword',
sprintf(
'<p class="cms-security__container__form__forgotPassword"><a href="%s" target="_top">%s</a></p>',
$this->getExternalLink('lostpassword'),
_t(__CLASS__.'.BUTTONFORGOTPASSWORD', "Forgot password")
)
)
]);
@ -111,6 +125,6 @@ class CMSMemberLoginForm extends MemberLoginForm
*/
public function getAuthenticatorName()
{
return _t('SilverStripe\\Security\\CMSMemberLoginForm.AUTHENTICATORNAME', 'CMS Member Login Form');
return _t(__CLASS__.'.AUTHENTICATORNAME', 'CMS Member Login Form');
}
}

View File

@ -156,9 +156,10 @@ class MemberLoginForm extends BaseLoginForm
_t('SilverStripe\\Security\\Member.KEEPMESIGNEDIN', "Keep me signed in")
)->setAttribute(
'title',
sprintf(
_t('SilverStripe\\Security\\Member.REMEMBERME', "Remember me next time? (for %d days on this device)"),
RememberLoginHash::config()->uninherited('token_expiry_days')
_t(
'SilverStripe\\Security\\Member.REMEMBERME',
"Remember me next time? (for {count} days on this device)",
[ 'count' => RememberLoginHash::config()->uninherited('token_expiry_days') ]
)
)
);

View File

@ -25,7 +25,6 @@ use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\View\ArrayData;
use SilverStripe\View\SSViewer;
use SilverStripe\View\TemplateGlobalProvider;
@ -241,7 +240,7 @@ class Security extends Controller implements TemplateGlobalProvider
public function index()
{
return $this->httpError(404); // no-op
$this->httpError(404); // no-op
}
/**
@ -358,7 +357,7 @@ class Security extends Controller implements TemplateGlobalProvider
$messageSet = $configMessageSet;
} else {
$messageSet = array(
'default' => _t(
'default' => _t(
'SilverStripe\\Security\\Security.NOTEPAGESECURED',
"That page is secured. Enter your credentials below and we will send "
. "you right along."
@ -608,6 +607,10 @@ class Security extends Controller implements TemplateGlobalProvider
*/
protected function generateLoginFormSet($forms)
{
if (count($forms) === 1) {
return $forms;
}
$viewData = new ArrayData(array(
'Forms' => new ArrayList($forms),
));
@ -773,6 +776,7 @@ class Security extends Controller implements TemplateGlobalProvider
return $this->renderWrappedController(
$title,
[
'Forms' => ArrayList::create($forms),
'Form' => $this->generateLoginFormSet($forms),
],
$templates
@ -1086,12 +1090,12 @@ class Security extends Controller implements TemplateGlobalProvider
// New salts will only need to be generated if the password is hashed for the first time
$salt = ($salt) ? $salt : $encryptor->salt($password);
return array(
return [
'password' => $encryptor->encrypt($password, $salt, $member),
'salt' => $salt,
'salt' => $salt,
'algorithm' => $algorithm,
'encryptor' => $encryptor
);
];
}
/**
@ -1238,12 +1242,12 @@ class Security extends Controller implements TemplateGlobalProvider
*/
public static function get_template_global_variables()
{
return array(
"LoginURL" => "login_url",
"LogoutURL" => "logout_url",
return [
"LoginURL" => "login_url",
"LogoutURL" => "logout_url",
"LostPasswordURL" => "lost_password_url",
"CurrentMember" => "getCurrentUser",
"currentUser" => "getCurrentUser"
);
"CurrentMember" => "getCurrentUser",
"currentUser" => "getCurrentUser"
];
}
}

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<% base_tag %>
<title><%t SilverStripe\\Security\\CMSSecurity.LOGIN_TITLE 'Return to where you left off by logging back in' %></title>
</head>
<body class="cms cms-security fill-height">
<% with $Form %>
<% if $Message %>
<div class="cms-security__container__error message $MessageType">
<p id="{$FormName}_error">$Message</p>
</div>
<% end_if %>
<% end_with %>
<div class="cms-security__container container fill-height">
<div class="row">
<h1>
<span class="icon font-icon-back-in-time"></span>
<%t SilverStripe\\Security\\CMSSecurity.LOGIN_TITLE 'Return to where you left off by logging back in' %>
</h1>
</div>
<% if $Content %>
<div class="row">
<div class="Content">$Content</div>
</div>
<% end_if %>
<div class="row">
<div class="cms-security__container__form">
$Form
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<% base_tag %>
<title><%t SilverStripe\\Security\\CMSSecurity.SUCCESS_TITLE 'Login successful' %></title>
</head>
<body class="cms cms-security">
<div class="cms-security__container container fill-height">
<div class="row">
<h1>
<span class="icon font-icon-back-in-time"></span>
<%t SilverStripe\\Security\\CMSSecurity.SUCCESS_TITLE 'Login successful' %>
</h1>
</div>
<% if $Content %>
<div class="row">
<div class="Content">$Content</div>
</div>
<% end_if %>
<div class="row">
<div class="cms-security__container__form">
$Form
</div>
</div>
</div>
<script type="text/javascript">
// Ensure top level section is updated
jQuery(function () {
var origin = window.location.origin ||
[
window.location.protocol,
"//",
window.location.hostname,
(window.location.port ? ':' + window.location.port : '')
].join('');
var securityID = '{$SecurityID.JS}';
var memberToken = '{$CurrentMember.TempIDHash.JS}';
window.top.postMessage(
JSON.stringify({
type: 'callback',
callback: 'reauthenticate',
target: '.leftandmain__login-dialog',
data: {
'SecurityID': securityID,
'TempID': memberToken
}
}),
origin
);
});
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<% if $IncludeFormTag %>
<form $AttributesHTML>
<% end_if %>
<fieldset>
<% if $Legend %><legend>$Legend</legend><% end_if %>
<% loop $Fields %>
$FieldHolder
<% end_loop %>
<div class="clear"><!-- --></div>
</fieldset>
<% if $Actions %>
<div class="btn-toolbar">
<% loop $Actions %>
$Field
<% end_loop %>
</div>
<% end_if %>
<% if $IncludeFormTag %>
</form>
<% end_if %>

View File

@ -0,0 +1,35 @@
@modal @retry
Feature: Reauthenticate
As a content editor
I want to be able to log in through a CMS popup when my session expires
So that I can avoid losing unsaved work
Background:
And I am logged in with "ADMIN" permissions
And I go to "/admin/security"
And I am not in an iframe
And I click the "Users" CMS tab
And my session expires
Scenario: Reauthenticate with correct login
When I press the "Add Member" button
And I switch to the "login-dialog-iframe" iframe
Then I should see "Your session has timed out due to inactivity" in the ".cms-security__container" element
When I fill in "Password" with "Secret!123"
And I press the "Let me back in" button
And I am not in an iframe
And I click "ADMIN" in the "#Root_Users" element
Then I should see "Save" in the "#Form_ItemEditForm_action_doSave" element
Scenario: Reauthenticate with wrong login
When I press the "Add Member" button
And I switch to the "login-dialog-iframe" iframe
Then I should see "Your session has timed out due to inactivity" in the ".cms-security__container" element
When I fill in "Password" with "wrong password"
And I press the "Let me back in" button
Then I should see "The provided details don't seem to be correct. Please try again."
When I fill in "Password" with "Secret!123"
And I press the "Let me back in" button
And I am not in an iframe
And I click "ADMIN" in the "#Root_Users" element
Then I should see "Save" in the "#Form_ItemEditForm_action_doSave" element

View File

@ -293,4 +293,31 @@ JS;
throw new BadMethodCallException("Invalid condition");
}
}
/**
* @When /^I switch to the "([^"]*)" iframe$/
* @param string $id iframe id property
*/
public function stepSwitchToTheFrame($id)
{
$this->getMainContext()->getSession()->getDriver()->switchToIFrame($id);
}
/**
* @When /^I am not in an iframe$/
*/
public function stepSwitchToParentFrame()
{
$this->getMainContext()->getSession()->getDriver()->switchToIFrame(null);
}
/**
* @When /^my session expires$/
*/
public function stepMySessionExpires()
{
// Destroy cookie to detach session
$this->getMainContext()->getSession()->setCookie('PHPSESSID', null);
}
}