2007-09-14 05:12:21 +02:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2017-04-22 06:30:10 +02:00
|
|
|
namespace SilverStripe\Security\MemberAuthenticator;
|
2016-06-23 01:37:22 +02:00
|
|
|
|
2017-06-09 05:07:35 +02:00
|
|
|
use InvalidArgumentException;
|
2017-06-22 12:50:45 +02:00
|
|
|
use SilverStripe\Control\HTTPRequest;
|
2017-06-13 11:04:43 +02:00
|
|
|
use SilverStripe\Core\Extensible;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\ValidationResult;
|
2017-05-30 09:42:00 +02:00
|
|
|
use SilverStripe\Security\Authenticator;
|
2017-06-22 12:50:45 +02:00
|
|
|
use SilverStripe\Security\DefaultAdminService;
|
2017-04-23 05:30:33 +02:00
|
|
|
use SilverStripe\Security\LoginAttempt;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Security\Member;
|
2017-06-15 04:20:12 +02:00
|
|
|
use SilverStripe\Security\PasswordEncryptor;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Security\Security;
|
2016-06-23 01:37:22 +02:00
|
|
|
|
2007-09-14 05:12:21 +02:00
|
|
|
/**
|
|
|
|
* Authenticator for the default "member" method
|
|
|
|
*
|
2017-05-30 09:42:00 +02:00
|
|
|
* @author Sam Minnee <sam@silverstripe.com>
|
|
|
|
* @author Simon Erkelens <simonerkelens@silverstripe.com>
|
2007-09-14 05:12:21 +02:00
|
|
|
*/
|
2017-05-30 09:42:00 +02:00
|
|
|
class MemberAuthenticator implements Authenticator
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-06-13 11:04:43 +02:00
|
|
|
use Extensible;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-22 06:30:10 +02:00
|
|
|
public function supportedServices()
|
|
|
|
{
|
2017-05-30 09:42:00 +02:00
|
|
|
// Bitwise-OR of all the supported services in this Authenticator, to make a bitmask
|
|
|
|
return Authenticator::LOGIN | Authenticator::LOGOUT | Authenticator::CHANGE_PASSWORD
|
2017-06-15 04:20:12 +02:00
|
|
|
| Authenticator::RESET_PASSWORD | Authenticator::CHECK_PASSWORD;
|
2017-04-22 06:30:10 +02:00
|
|
|
}
|
|
|
|
|
2017-06-22 12:50:45 +02:00
|
|
|
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
|
2017-04-22 06:30:10 +02:00
|
|
|
{
|
|
|
|
// Find authenticated member
|
2017-05-30 09:42:00 +02:00
|
|
|
$member = $this->authenticateMember($data, $result);
|
2017-04-22 06:30:10 +02:00
|
|
|
|
|
|
|
// Optionally record every login attempt as a {@link LoginAttempt} object
|
2017-06-22 12:50:45 +02:00
|
|
|
$this->recordLoginAttempt($data, $request, $member, $result->isValid());
|
2017-04-22 06:30:10 +02:00
|
|
|
|
|
|
|
if ($member) {
|
2017-06-22 12:50:45 +02:00
|
|
|
$request->getSession()->clear('BackURL');
|
2017-04-22 06:30:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-30 09:42:00 +02:00
|
|
|
return $result->isValid() ? $member : null;
|
2017-04-22 06:30:10 +02:00
|
|
|
}
|
2016-11-23 06:09:10 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to find and authenticate member if possible from the given data
|
|
|
|
*
|
2017-07-03 02:21:27 +02:00
|
|
|
* @skipUpgrade
|
2017-04-30 05:17:26 +02:00
|
|
|
* @param array $data Form submitted data
|
2017-05-30 09:42:00 +02:00
|
|
|
* @param ValidationResult $result
|
2017-06-15 07:25:23 +02:00
|
|
|
* @param Member $member This third parameter is used in the CMSAuthenticator(s)
|
|
|
|
* @return Member Found member, regardless of successful login
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2017-06-15 07:25:23 +02:00
|
|
|
protected function authenticateMember($data, ValidationResult &$result = null, Member $member = null)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-05-30 09:42:00 +02:00
|
|
|
$email = !empty($data['Email']) ? $data['Email'] : null;
|
2017-06-15 04:20:12 +02:00
|
|
|
$result = $result ?: ValidationResult::create();
|
2017-05-30 09:42:00 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Check default login (see Security::setDefaultAdmin())
|
2017-06-15 04:20:12 +02:00
|
|
|
$asDefaultAdmin = DefaultAdminService::isDefaultAdmin($email);
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($asDefaultAdmin) {
|
|
|
|
// If logging is as default admin, ensure record is setup correctly
|
2017-06-15 04:20:12 +02:00
|
|
|
$member = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
|
|
|
|
$member->validateCanLogin($result);
|
|
|
|
if ($result->isValid()) {
|
|
|
|
// Check if default admin credentials are correct
|
|
|
|
if (DefaultAdminService::isDefaultAdminCredentials($email, $data['Password'])) {
|
|
|
|
return $member;
|
|
|
|
} else {
|
|
|
|
$result->addError(_t(
|
|
|
|
'SilverStripe\\Security\\Member.ERRORWRONGCRED',
|
|
|
|
"The provided details don't seem to be correct. Please try again."
|
|
|
|
));
|
|
|
|
}
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Attempt to identify user by email
|
|
|
|
if (!$member && $email) {
|
|
|
|
// Find user by email
|
2017-06-13 11:04:43 +02:00
|
|
|
$identifierField = Member::config()->get('unique_identifier_field');
|
2017-04-30 05:17:26 +02:00
|
|
|
/** @var Member $member */
|
2016-11-23 06:09:10 +01:00
|
|
|
$member = Member::get()
|
2017-06-13 11:04:43 +02:00
|
|
|
->filter([$identifierField => $email])
|
2016-11-23 06:09:10 +01:00
|
|
|
->first();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate against member if possible
|
|
|
|
if ($member && !$asDefaultAdmin) {
|
2017-06-15 04:20:12 +02:00
|
|
|
$this->checkPassword($member, $data['Password'], $result);
|
2018-04-23 00:29:10 +02:00
|
|
|
} elseif (!$asDefaultAdmin) {
|
|
|
|
// spoof a login attempt
|
|
|
|
$tempMember = Member::create();
|
|
|
|
$tempMember->{Member::config()->get('unique_identifier_field')} = $email;
|
|
|
|
$tempMember->validateCanLogin($result);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Emit failure to member and form (if available)
|
2017-05-30 09:42:00 +02:00
|
|
|
if (!$result->isValid()) {
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($member) {
|
|
|
|
$member->registerFailedLogin();
|
|
|
|
}
|
2017-06-15 04:20:12 +02:00
|
|
|
} elseif ($member) {
|
2017-05-09 22:24:15 +02:00
|
|
|
$member->registerSuccessfulLogin();
|
2017-06-23 01:19:16 +02:00
|
|
|
} else {
|
|
|
|
// A non-existing member occurred. This will make the result "valid" so let's invalidate
|
|
|
|
$result->addError(_t(
|
|
|
|
'SilverStripe\\Security\\Member.ERRORWRONGCRED',
|
|
|
|
"The provided details don't seem to be correct. Please try again."
|
|
|
|
));
|
2017-06-15 04:20:12 +02:00
|
|
|
return null;
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $member;
|
|
|
|
}
|
|
|
|
|
2017-06-13 11:04:43 +02:00
|
|
|
/**
|
|
|
|
* Check if the passed password matches the stored one (if the member is not locked out).
|
|
|
|
*
|
|
|
|
* Note, we don't return early, to prevent differences in timings to give away if a member
|
|
|
|
* password is invalid.
|
|
|
|
*
|
|
|
|
* @param Member $member
|
2017-06-15 04:20:12 +02:00
|
|
|
* @param string $password
|
|
|
|
* @param ValidationResult $result
|
2017-06-13 11:04:43 +02:00
|
|
|
* @return ValidationResult
|
|
|
|
*/
|
2017-06-15 07:25:23 +02:00
|
|
|
public function checkPassword(Member $member, $password, ValidationResult &$result = null)
|
2017-06-13 11:04:43 +02:00
|
|
|
{
|
2017-06-15 04:20:12 +02:00
|
|
|
// Check if allowed to login
|
|
|
|
$result = $member->validateCanLogin($result);
|
|
|
|
if (!$result->isValid()) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allow default admin to login as self
|
|
|
|
if (DefaultAdminService::isDefaultAdminCredentials($member->Email, $password)) {
|
|
|
|
return $result;
|
|
|
|
}
|
2017-06-13 11:04:43 +02:00
|
|
|
|
|
|
|
// Check a password is set on this member
|
|
|
|
if (empty($member->Password) && $member->exists()) {
|
|
|
|
$result->addError(_t(__CLASS__ . '.NoPassword', 'There is no password on this member.'));
|
2017-06-23 01:19:16 +02:00
|
|
|
}
|
2017-06-13 11:04:43 +02:00
|
|
|
|
|
|
|
$encryptor = PasswordEncryptor::create_for_algorithm($member->PasswordEncryption);
|
|
|
|
if (!$encryptor->check($member->Password, $password, $member->Salt, $member)) {
|
|
|
|
$result->addError(_t(
|
|
|
|
__CLASS__ . '.ERRORWRONGCRED',
|
|
|
|
'The provided details don\'t seem to be correct. Please try again.'
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Log login attempt
|
|
|
|
* TODO We could handle this with an extension
|
|
|
|
*
|
|
|
|
* @param array $data
|
2017-06-22 12:50:45 +02:00
|
|
|
* @param HTTPRequest $request
|
2016-11-23 06:09:10 +01:00
|
|
|
* @param Member $member
|
2017-05-30 09:42:00 +02:00
|
|
|
* @param boolean $success
|
2019-09-18 03:33:55 +02:00
|
|
|
* @return LoginAttempt|null
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2017-06-22 12:50:45 +02:00
|
|
|
protected function recordLoginAttempt($data, HTTPRequest $request, $member, $success)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2018-04-23 00:29:10 +02:00
|
|
|
if (!Security::config()->get('login_recording')
|
|
|
|
&& !Member::config()->get('lock_out_after_incorrect_logins')
|
|
|
|
) {
|
2019-09-18 03:33:55 +02:00
|
|
|
return null;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// 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");
|
|
|
|
}
|
|
|
|
|
2017-04-30 05:17:26 +02:00
|
|
|
$attempt = LoginAttempt::create();
|
2017-05-30 09:42:00 +02:00
|
|
|
if ($success && $member) {
|
2016-11-23 06:09:10 +01:00
|
|
|
// successful login (member is existing with matching password)
|
|
|
|
$attempt->MemberID = $member->ID;
|
2017-05-09 22:24:15 +02:00
|
|
|
$attempt->Status = LoginAttempt::SUCCESS;
|
2016-11-23 06:09:10 +01:00
|
|
|
|
|
|
|
// Audit logging hook
|
2017-06-13 11:04:43 +02:00
|
|
|
$member->extend('authenticationSucceeded');
|
2016-11-23 06:09:10 +01:00
|
|
|
} else {
|
|
|
|
// Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
|
2017-05-09 22:24:15 +02:00
|
|
|
$attempt->Status = LoginAttempt::FAILURE;
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($member) {
|
|
|
|
// Audit logging hook
|
|
|
|
$attempt->MemberID = $member->ID;
|
2017-06-22 12:50:45 +02:00
|
|
|
$member->extend('authenticationFailed', $data, $request);
|
2016-11-23 06:09:10 +01:00
|
|
|
} else {
|
|
|
|
// Audit logging hook
|
2017-06-22 12:50:45 +02:00
|
|
|
Member::singleton()
|
|
|
|
->extend('authenticationFailedUnknownUser', $data, $request);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$attempt->Email = $email;
|
2017-06-22 12:50:45 +02:00
|
|
|
$attempt->IP = $request->getIP();
|
2019-09-18 03:33:55 +02:00
|
|
|
|
|
|
|
$this->invokeWithExtensions('updateLoginAttempt', $attempt, $data, $request);
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
$attempt->write();
|
2019-09-18 03:33:55 +02:00
|
|
|
|
|
|
|
return $attempt;
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-09 05:07:35 +02:00
|
|
|
* @param string $link
|
2017-05-30 09:42:00 +02:00
|
|
|
* @return LostPasswordHandler
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2017-04-22 06:30:10 +02:00
|
|
|
public function getLostPasswordHandler($link)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-22 06:30:10 +02:00
|
|
|
return LostPasswordHandler::create($link, $this);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
2017-05-30 09:42:00 +02:00
|
|
|
* @param string $link
|
|
|
|
* @return ChangePasswordHandler
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2017-04-22 06:30:10 +02:00
|
|
|
public function getChangePasswordHandler($link)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-22 06:30:10 +02:00
|
|
|
return ChangePasswordHandler::create($link, $this);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-22 06:30:10 +02:00
|
|
|
/**
|
2017-05-30 09:42:00 +02:00
|
|
|
* @param string $link
|
|
|
|
* @return LoginHandler
|
2017-04-22 06:30:10 +02:00
|
|
|
*/
|
|
|
|
public function getLoginHandler($link)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-22 06:30:10 +02:00
|
|
|
return LoginHandler::create($link, $this);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-30 05:17:26 +02:00
|
|
|
/**
|
2017-05-30 09:42:00 +02:00
|
|
|
* @param string $link
|
|
|
|
* @return LogoutHandler
|
2017-04-30 05:17:26 +02:00
|
|
|
*/
|
2017-05-20 06:32:25 +02:00
|
|
|
public function getLogoutHandler($link)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-05-20 06:32:25 +02:00
|
|
|
return LogoutHandler::create($link, $this);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2007-09-14 05:12:21 +02:00
|
|
|
}
|