silverstripe-framework/src/Security/MemberAuthenticator/Authenticator.php

224 lines
7.0 KiB
PHP
Raw Normal View History

<?php
namespace SilverStripe\Security\MemberAuthenticator;
2016-06-23 01:37:22 +02:00
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie;
use SilverStripe\Control\Session;
use SilverStripe\ORM\ValidationResult;
2016-06-23 01:37:22 +02:00
use InvalidArgumentException;
use SilverStripe\Security\Authenticator as BaseAuthenticator;
use SilverStripe\Security\RememberLoginHash;
use SilverStripe\Security\Security;
use SilverStripe\Security\Member;
use SilverStripe\Security\LoginAttempt;
2016-06-23 01:37:22 +02:00
/**
* Authenticator for the default "member" method
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
class Authenticator implements BaseAuthenticator
2016-11-29 00:31:16 +01:00
{
public function supportedServices()
{
// Bitwise-OR of all the supported services, to make a bitmask
return BaseAuthenticator::LOGIN | BaseAuthenticator::LOGOUT | BaseAuthenticator::CHANGE_PASSWORD
| BaseAuthenticator::RESET_PASSWORD;
}
/**
* @inherit
*/
public function authenticate($data, &$message)
{
$success = null;
// Find authenticated member
$member = $this->authenticateMember($data, $message, $success);
// Optionally record every login attempt as a {@link LoginAttempt} object
$this->recordLoginAttempt($data, $member, $success);
if ($member) {
Session::clear('BackURL');
}
return $success ? $member : null;
}
/**
* Attempt to find and authenticate member if possible from the given data
*
* @param array $data Form submitted data
* @param $message
* @param bool &$success Success flag
* @param null|Member $member If the parent method already identified the member, it can be passed in
* @return Member Found member, regardless of successful login
*/
protected function authenticateMember($data, &$message, &$success, $member = null)
2016-11-29 00:31:16 +01:00
{
// Default success to false
$success = false;
$email = !empty($data['Email']) ? $data['Email'] : null ;
// Check default login (see Security::setDefaultAdmin())
$asDefaultAdmin = $email === Security::default_admin_username();
if ($asDefaultAdmin) {
// If logging is as default admin, ensure record is setup correctly
$member = Member::default_admin();
$success = $member->canLogin()->isValid() && Security::check_default_admin($email, $data['Password']);
//protect against failed login
if ($success) {
return $member;
}
2016-11-29 00:31:16 +01:00
}
// Attempt to identify user by email
if (!$member && $email) {
// Find user by email
/** @var Member $member */
$member = Member::get()
->filter([Member::config()->get('unique_identifier_field') => $email])
->first();
}
// Validate against member if possible
if ($member && !$asDefaultAdmin) {
$result = $member->checkPassword($data['Password']);
$success = $result->isValid();
} else {
$result = ValidationResult::create()->addError(_t(
2017-04-20 03:15:24 +02:00
'SilverStripe\\Security\\Member.ERRORWRONGCRED',
'The provided details don\'t seem to be correct. Please try again.'
));
}
// Emit failure to member and form (if available)
if (!$success) {
if ($member) {
$member->registerFailedLogin();
}
$message = implode("; ", array_map(
function ($message) {
return $message['message'];
},
$result->getMessages()
));
} else {
if ($member) { // How can success be true and member false?
2016-11-29 00:31:16 +01:00
$member->registerSuccessfulLogin();
}
}
return $member;
}
/**
* Log login attempt
* TODO We could handle this with an extension
*
* @param array $data
* @param Member $member
*/
protected function recordLoginAttempt($data, $member, $success)
2016-11-29 00:31:16 +01:00
{
if (!Security::config()->get('login_recording')) {
2016-11-29 00:31:16 +01:00
return;
}
// Check email is valid
/** @skipUpgrade */
$email = isset($data['Email']) ? $data['Email'] : null;
if (is_array($email)) {
throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
}
$attempt = LoginAttempt::create();
if ($success) {
// successful login (member is existing with matching password)
$attempt->MemberID = $member->ID;
$attempt->Status = 'Success';
// Audit logging hook
$member->extend('authenticated');
} else {
// Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
$attempt->Status = 'Failure';
if ($member) {
// Audit logging hook
$attempt->MemberID = $member->ID;
$member->extend('authenticationFailed');
} else {
// Audit logging hook
Member::singleton()->extend('authenticationFailedUnknownUser', $data);
}
}
$attempt->Email = $email;
$attempt->IP = Controller::curr()->getRequest()->getIP();
$attempt->write();
}
/**
* @inherit
*/
public function getLostPasswordHandler($link)
2016-11-29 00:31:16 +01:00
{
return LostPasswordHandler::create($link, $this);
}
2016-11-29 00:31:16 +01:00
/**
* @inherit
*/
public function getChangePasswordHandler($link)
2016-11-29 00:31:16 +01:00
{
return ChangePasswordHandler::create($link, $this);
}
2016-11-29 00:31:16 +01:00
/**
* @inherit
*/
public function getLoginHandler($link)
2016-11-29 00:31:16 +01:00
{
return LoginHandler::create($link, $this);
}
2016-11-29 00:31:16 +01:00
/**
*
* @param Member $member
* @return bool|Member
*/
public function doLogOut(&$member)
2016-11-29 00:31:16 +01:00
{
if($member instanceof Member) {
Session::clear("loggedInAs");
if (Member::config()->login_marker_cookie) {
Cookie::set(Member::config()->login_marker_cookie, null, 0);
}
Session::destroy();
// Clears any potential previous hashes for this member
RememberLoginHash::clear($member, 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');
// Log out unsuccessful. Useful for 3rd-party logins that return failure. Shouldn't happen
// on the default authenticator though.
if(Member::currentUserID()) {
return Member::currentUser();
}
}
return true;
}
}