2007-09-14 05:12:21 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Authenticator for the default "member" method
|
|
|
|
*
|
|
|
|
* @author Markus Lanthaler <markus@silverstripe.com>
|
2012-04-12 08:02:46 +02:00
|
|
|
* @package framework
|
2008-02-25 03:10:37 +01:00
|
|
|
* @subpackage security
|
2007-09-14 05:12:21 +02:00
|
|
|
*/
|
|
|
|
class MemberAuthenticator extends Authenticator {
|
|
|
|
|
2009-11-06 03:23:30 +01:00
|
|
|
/**
|
2013-06-21 00:32:08 +02:00
|
|
|
* Contains encryption algorithm identifiers.
|
|
|
|
* If set, will migrate to new precision-safe password hashing
|
|
|
|
* upon login. See http://open.silverstripe.org/ticket/3004
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2013-06-21 00:32:08 +02:00
|
|
|
* @var array
|
2009-11-06 03:23:30 +01:00
|
|
|
*/
|
2013-03-21 19:48:54 +01:00
|
|
|
private static $migrate_legacy_hashes = array(
|
2014-08-15 08:53:05 +02:00
|
|
|
'md5' => 'md5_v2.4',
|
2009-11-06 03:23:30 +01:00
|
|
|
'sha1' => 'sha1_v2.4'
|
|
|
|
);
|
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
/**
|
2014-10-06 05:01:33 +02:00
|
|
|
* Attempt to find and authenticate member if possible from the given data
|
2012-12-08 12:20:20 +01:00
|
|
|
*
|
2014-10-06 05:01:33 +02:00
|
|
|
* @param array $data
|
|
|
|
* @param Form $form
|
|
|
|
* @param bool &$success Success flag
|
|
|
|
* @return Member Found member, regardless of successful login
|
2012-12-08 12:20:20 +01:00
|
|
|
*/
|
2014-10-06 05:01:33 +02:00
|
|
|
protected static function authenticate_member($data, $form, &$success) {
|
|
|
|
// Default success to false
|
|
|
|
$success = false;
|
|
|
|
|
|
|
|
// Attempt to identify by temporary ID
|
|
|
|
$member = null;
|
|
|
|
$email = null;
|
|
|
|
if(!empty($data['tempid'])) {
|
|
|
|
// Find user by tempid, in case they are re-validating an existing session
|
|
|
|
$member = Member::member_from_tempid($data['tempid']);
|
|
|
|
if($member) $email = $member->Email;
|
2010-10-13 03:35:19 +02:00
|
|
|
}
|
2010-02-05 01:36:25 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Otherwise, get email from posted value instead
|
|
|
|
if(!$member && !empty($data['Email'])) {
|
|
|
|
$email = $data['Email'];
|
2010-10-13 03:35:19 +02:00
|
|
|
}
|
2012-12-08 12:20:20 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Check default login (see Security::setDefaultAdmin())
|
2017-06-12 11:37:24 +02:00
|
|
|
$asDefaultAdmin = Security::has_default_admin() && $email === Security::default_admin_username();
|
2014-10-06 05:01:33 +02:00
|
|
|
if($asDefaultAdmin) {
|
|
|
|
// If logging is as default admin, ensure record is setup correctly
|
|
|
|
$member = Member::default_admin();
|
2017-06-12 11:37:24 +02:00
|
|
|
$success = Security::check_default_admin($email, $data['Password']) && $member && !$member->isLockedOut();
|
2016-04-18 19:35:14 +02:00
|
|
|
//protect against failed login
|
|
|
|
if($success) {
|
|
|
|
return $member;
|
|
|
|
}
|
2014-10-06 05:01:33 +02:00
|
|
|
}
|
2012-12-08 12:20:20 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Attempt to identify user by email
|
|
|
|
if(!$member && $email) {
|
|
|
|
// Find user by email
|
2013-06-21 00:32:08 +02:00
|
|
|
$member = Member::get()
|
2014-10-06 05:01:33 +02:00
|
|
|
->filter(Member::config()->unique_identifier_field, $email)
|
2013-06-21 00:32:08 +02:00
|
|
|
->first();
|
2014-10-06 05:01:33 +02:00
|
|
|
}
|
2012-12-08 12:20:20 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Validate against member if possible
|
|
|
|
if($member && !$asDefaultAdmin) {
|
|
|
|
$result = $member->checkPassword($data['Password']);
|
|
|
|
$success = $result->valid();
|
2017-05-09 22:24:15 +02:00
|
|
|
} elseif (!$asDefaultAdmin) {
|
|
|
|
// spoof a login attempt
|
|
|
|
$member = Member::create();
|
|
|
|
$member->Email = $email;
|
|
|
|
$member->{Member::config()->unique_identifier_field} = $data['Password'] . '-wrong';
|
|
|
|
$member->PasswordEncryption = 'none';
|
|
|
|
$result = $member->checkPassword($data['Password']);
|
|
|
|
$member = null;
|
2014-11-18 23:05:07 +01:00
|
|
|
} else {
|
2014-10-06 05:01:33 +02:00
|
|
|
$result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
|
|
|
|
}
|
2012-12-08 12:20:20 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Emit failure to member and form (if available)
|
|
|
|
if(!$success) {
|
|
|
|
if($member) $member->registerFailedLogin();
|
|
|
|
if($form) $form->sessionMessage($result->message(), 'bad');
|
2014-10-24 02:43:39 +02:00
|
|
|
} else {
|
|
|
|
if($member) $member->registerSuccessfulLogin();
|
2012-12-08 12:20:20 +01:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
return $member;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log login attempt
|
|
|
|
* TODO We could handle this with an extension
|
|
|
|
*
|
|
|
|
* @param array $data
|
|
|
|
* @param Member $member
|
|
|
|
* @param bool $success
|
|
|
|
*/
|
|
|
|
protected static function record_login_attempt($data, $member, $success) {
|
2017-05-09 22:24:15 +02:00
|
|
|
if(!Security::config()->login_recording && !Member::config()->lock_out_after_incorrect_logins) return;
|
2014-10-06 05:01:33 +02:00
|
|
|
|
|
|
|
// Check email is valid
|
|
|
|
$email = isset($data['Email']) ? $data['Email'] : null;
|
|
|
|
if(is_array($email)) {
|
|
|
|
throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
|
2012-12-08 12:20:20 +01:00
|
|
|
}
|
2014-10-06 05:01:33 +02:00
|
|
|
|
|
|
|
$attempt = new LoginAttempt();
|
|
|
|
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';
|
2012-12-08 12:20:20 +01:00
|
|
|
if($member) {
|
2008-08-12 22:59:32 +02:00
|
|
|
// Audit logging hook
|
2012-12-08 12:20:20 +01:00
|
|
|
$attempt->MemberID = $member->ID;
|
2014-10-06 05:01:33 +02:00
|
|
|
$member->extend('authenticationFailed');
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
} else {
|
2014-10-06 05:01:33 +02:00
|
|
|
// Audit logging hook
|
|
|
|
singleton('Member')->extend('authenticationFailedUnknownUser', $data);
|
2014-11-18 23:05:07 +01:00
|
|
|
}
|
2008-08-11 05:39:14 +02:00
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
$attempt->Email = $email;
|
|
|
|
$attempt->IP = Controller::curr()->getRequest()->getIP();
|
|
|
|
$attempt->write();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method to authenticate an user
|
|
|
|
*
|
|
|
|
* @param array $data Raw data to authenticate the user
|
|
|
|
* @param Form $form Optional: If passed, better error messages can be
|
|
|
|
* produced by using
|
|
|
|
* {@link Form::sessionMessage()}
|
|
|
|
* @return bool|Member Returns FALSE if authentication fails, otherwise
|
|
|
|
* the member object
|
|
|
|
* @see Security::setDefaultAdmin()
|
|
|
|
*/
|
|
|
|
public static function authenticate($data, Form $form = null) {
|
2017-09-04 18:11:02 +02:00
|
|
|
// minimum execution time for authenticating a member
|
|
|
|
$minExecTime = LoginForm::config()->min_auth_time / 1000;
|
|
|
|
$startTime = microtime(true);
|
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Find authenticated member
|
|
|
|
$member = static::authenticate_member($data, $form, $success);
|
2016-01-06 00:34:58 +01:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
// Optionally record every login attempt as a {@link LoginAttempt} object
|
|
|
|
static::record_login_attempt($data, $member, $success);
|
2016-01-06 00:34:58 +01:00
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
// Legacy migration to precision-safe password hashes.
|
|
|
|
// A login-event with cleartext passwords is the only time
|
|
|
|
// when we can rehash passwords to a different hashing algorithm,
|
|
|
|
// bulk-migration doesn't work due to the nature of hashing.
|
|
|
|
// See PasswordEncryptor_LegacyPHPHash class.
|
2017-07-26 10:53:54 +02:00
|
|
|
$migrateLegacyHashes = self::config()->migrate_legacy_hashes;
|
|
|
|
if($success && $member && isset($migrateLegacyHashes[$member->PasswordEncryption])) {
|
2014-10-06 05:01:33 +02:00
|
|
|
$member->Password = $data['Password'];
|
2017-07-26 10:53:54 +02:00
|
|
|
$member->PasswordEncryption = $migrateLegacyHashes[$member->PasswordEncryption];
|
2012-12-08 12:20:20 +01:00
|
|
|
$member->write();
|
|
|
|
}
|
2007-09-14 05:12:21 +02:00
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
if($success) Session::clear('BackURL');
|
2007-09-14 05:12:21 +02:00
|
|
|
|
2017-09-04 18:11:02 +02:00
|
|
|
$waitFor = $minExecTime - (microtime(true) - $startTime);
|
|
|
|
if ($waitFor > 0) {
|
|
|
|
usleep($waitFor * 1000000);
|
|
|
|
}
|
|
|
|
|
2014-10-06 05:01:33 +02:00
|
|
|
return $success ? $member : null;
|
2010-02-05 01:36:25 +01:00
|
|
|
}
|
2007-09-14 05:12:21 +02:00
|
|
|
|
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
/**
|
|
|
|
* Method that creates the login form for this authentication method
|
|
|
|
*
|
|
|
|
* @param Controller The parent controller, necessary to create the
|
|
|
|
* appropriate form action tag
|
2017-09-12 16:57:03 +02:00
|
|
|
* @return MemberLoginForm Returns the login form to use with this authentication
|
2012-12-08 12:20:20 +01:00
|
|
|
* method
|
|
|
|
*/
|
|
|
|
public static function get_login_form(Controller $controller) {
|
2014-10-06 05:01:33 +02:00
|
|
|
return MemberLoginForm::create($controller, "LoginForm");
|
|
|
|
}
|
|
|
|
|
2017-09-12 16:57:03 +02:00
|
|
|
/**
|
|
|
|
* @param Controller $controller
|
|
|
|
* @return CMSMemberLoginForm
|
|
|
|
*/
|
|
|
|
public static function get_cms_login_form(Controller $controller) {
|
2014-10-06 05:01:33 +02:00
|
|
|
return CMSMemberLoginForm::create($controller, "LoginForm");
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function supports_cms() {
|
|
|
|
// Don't automatically support subclasses of MemberAuthenticator
|
|
|
|
return get_called_class() === __CLASS__;
|
2012-12-08 12:20:20 +01:00
|
|
|
}
|
2007-09-14 21:13:12 +02:00
|
|
|
|
|
|
|
|
2012-12-08 12:20:20 +01:00
|
|
|
/**
|
|
|
|
* Get the name of the authentication method
|
|
|
|
*
|
|
|
|
* @return string Returns the name of the authentication method.
|
|
|
|
*/
|
|
|
|
public static function get_name() {
|
2007-10-25 04:47:45 +02:00
|
|
|
return _t('MemberAuthenticator.TITLE', "E-mail & Password");
|
2007-09-14 21:13:12 +02:00
|
|
|
}
|
2007-09-14 05:12:21 +02:00
|
|
|
}
|
|
|
|
|