AuthDb.php (mrbs-1.9.4) | : | AuthDb.php (mrbs-1.10.0) | ||
---|---|---|---|---|
<?php | <?php | |||
namespace MRBS\Auth; | namespace MRBS\Auth; | |||
use MRBS\MailQueue; | use MRBS\MailQueue; | |||
use MRBS\User; | use MRBS\User; | |||
use PHPMailer\PHPMailer\PHPMailer; | use PHPMailer\PHPMailer\PHPMailer; | |||
use function MRBS\_tbl; | ||||
use function MRBS\auth; | ||||
use function MRBS\db; | ||||
use function MRBS\generate_global_uid; | ||||
use function MRBS\generate_token; | ||||
use function MRBS\get_mail_charset; | ||||
use function MRBS\get_vocab; | ||||
use function MRBS\multisite; | ||||
use function MRBS\parse_email; | ||||
use function MRBS\toTimeString; | ||||
use function MRBS\url_base; | ||||
use function MRBS\utf8_strpos; | ||||
use function MRBS\utf8_strtolower; | ||||
class AuthDb extends Auth | class AuthDb extends Auth | |||
{ | { | |||
/* validateUser($user, $pass) | /* validateUser($user, $pass) | |||
* | * | |||
* Checks if the specified username/password pair are valid | * Checks if the specified username/password pair are valid | |||
* | * | |||
* $user - The user name | * $user - The user name | |||
* $pass - The password | * $pass - The password | |||
* | * | |||
* Returns: | * Returns: | |||
* false - The pair are invalid or do not exist | * false - The pair are invalid or do not exist | |||
* string - The validated username | * string - The validated username | |||
*/ | */ | |||
public function validateUser($user, $pass) | public function validateUser(?string $user, ?string $pass) | |||
{ | { | |||
// The string $user that the user logged on with could be either a username or | // The string $user that the user logged on with could be either a username or | |||
// an email address, or even possibly just the local part of an email addres s. | // an email address, or even possibly just the local part of an email addres s. | |||
// So it's just possible that there is more than one user with this password and | // So it's just possible that there is more than one user with this password and | |||
// username | email address | local-part. If we get more than one, then w e don't | // username | email address | local-part. If we get more than one, then w e don't | |||
// know which user it is, so we return false. | // know which user it is, so we return false. | |||
$valid_usernames = array(); | $valid_usernames = array(); | |||
if (($valid_username = $this->validateUsername($user, $pass)) !== false) | if (($valid_username = $this->validateUsername($user, $pass)) !== false) | |||
{ | { | |||
skipping to change at line 43 | skipping to change at line 56 | |||
} | } | |||
$valid_usernames = array_merge($valid_usernames, $this->validateEmail($user, $pass)); | $valid_usernames = array_merge($valid_usernames, $this->validateEmail($user, $pass)); | |||
$valid_usernames = array_unique($valid_usernames); | $valid_usernames = array_unique($valid_usernames); | |||
if (count($valid_usernames) == 1) | if (count($valid_usernames) == 1) | |||
{ | { | |||
$result = $valid_usernames[0]; | $result = $valid_usernames[0]; | |||
// Update the database with this login, but don't change the timestamp | // Update the database with this login, but don't change the timestamp | |||
$now = time(); | $now = time(); | |||
$sql = "UPDATE " . \MRBS\_tbl('users') . " | $sql = "UPDATE " . _tbl('users') . " | |||
SET last_login=?, timestamp=timestamp | SET last_login=?, timestamp=timestamp | |||
WHERE name=?"; | WHERE name=?"; | |||
$sql_params = array($now, $result); | $sql_params = array($now, $result); | |||
\MRBS\db()->command($sql, $sql_params); | db()->command($sql, $sql_params); | |||
return $result; | return $result; | |||
} | } | |||
else | else | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
} | } | |||
/* validateUsername($user, $pass) | /* validateUsername($user, $pass) | |||
* | * | |||
* Checks if the specified username/password pair are valid | * Checks if the specified username/password pair are valid | |||
* | * | |||
* $user - The user name | * $user - The user name | |||
* $pass - The password | * $pass - The password | |||
* | * | |||
* Returns: | * Returns: | |||
* false - The pair are invalid or do not exist | * false - The pair are invalid or do not exist | |||
* string - The validated username | * string - The validated username | |||
*/ | */ | |||
private function validateUsername($user, $pass) | private function validateUsername(string $user, string $pass) | |||
{ | { | |||
$sql_params = array(); | $sql_params = array(); | |||
// We use syntax_casesensitive_equals() rather than just '=' because '=' in MySQL | // We use syntax_casesensitive_equals() rather than just '=' because '=' in MySQL | |||
// permits trailing spacings, eg 'john' = 'john '. We could use LIKE, but that then | // permits trailing spacings, eg 'john' = 'john '. We could use LIKE, but that then | |||
// permits wildcards, so we could use a combination of LIKE and '=' but that 's a bit | // permits wildcards, so we could use a combination of LIKE and '=' but that 's a bit | |||
// messy. WE could use STRCMP, but that's MySQL only. | // messy. WE could use STRCMP, but that's MySQL only. | |||
// Usernames are unique in the users table, so we only look for one. | // Usernames are unique in the users table, so we only look for one. | |||
$sql = "SELECT password_hash, name | $sql = "SELECT password_hash, name | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
WHERE " . \MRBS\db()->syntax_casesensitive_equals('name', \MRBS\utf8_ | WHERE " . db()->syntax_casesensitive_equals('name', utf8_strtolower($ | |||
strtolower($user), $sql_params) . " | user), $sql_params) . " | |||
LIMIT 1"; | LIMIT 1"; | |||
$res = \MRBS\db()->query($sql, $sql_params); | $res = db()->query($sql, $sql_params); | |||
$row = $res->next_row_keyed(); | $row = $res->next_row_keyed(); | |||
if (!isset($row['password_hash'])) | if (!isset($row['password_hash'])) | |||
{ | { | |||
// No user found with that name | // No user found with that name | |||
return false; | return false; | |||
} | } | |||
return ($this->checkPassword($pass, $row['password_hash'], 'name', $row['nam e'])) ? $row['name'] : false; | return ($this->checkPassword($pass, $row['password_hash'], 'name', $row['nam e'])) ? $row['name'] : false; | |||
skipping to change at line 105 | skipping to change at line 118 | |||
/* authValidateEmail($email, $pass) | /* authValidateEmail($email, $pass) | |||
* | * | |||
* Checks if the specified email/password pair are valid | * Checks if the specified email/password pair are valid | |||
* | * | |||
* $email - The email address | * $email - The email address | |||
* $pass - The password | * $pass - The password | |||
* | * | |||
* Returns: | * Returns: | |||
* array - An array of valid usernames, empty if none found | * array - An array of valid usernames, empty if none found | |||
*/ | */ | |||
private function validateEmail($email, $pass) | private function validateEmail(string $email, string $pass) : array | |||
{ | { | |||
$valid_usernames = array(); | $valid_usernames = array(); | |||
// Email addresses are not unique in the users table, so we need to find all of them. | // Email addresses are not unique in the users table, so we need to find all of them. | |||
$users = self::getUsersByEmail($email); | $users = self::getUsersByEmail($email); | |||
// Check all the users that have this email address and password hash. | // Check all the users that have this email address and password hash. | |||
foreach($users as $user) | foreach($users as $user) | |||
{ | { | |||
if ($this->checkPassword($pass, $user['password_hash'], 'email', $email)) | if (isset($user['password_hash']) && | |||
$this->checkPassword($pass, $user['password_hash'], 'email', $email)) | ||||
{ | { | |||
$valid_usernames[] = $user['name']; | $valid_usernames[] = $user['name']; | |||
} | } | |||
} | } | |||
return $valid_usernames; | return $valid_usernames; | |||
} | } | |||
public function getUser($username) | public function getUser(string $username) : ?User | |||
{ | { | |||
$row = $this->getUserByUsername($username); | $row = $this->getUserByUsername($username); | |||
// The username doesn't exist - return NULL | // The username doesn't exist - return NULL | |||
if (!isset($row)) | if (!isset($row)) | |||
{ | { | |||
return null; | return null; | |||
} | } | |||
// The username does exist - return a User object | // The username does exist - return a User object | |||
skipping to change at line 152 | skipping to change at line 166 | |||
// This has already been set as the 'username' property; | // This has already been set as the 'username' property; | |||
continue; | continue; | |||
} | } | |||
$user->$key = $value; | $user->$key = $value; | |||
} | } | |||
return $user; | return $user; | |||
} | } | |||
// Return an array of users, indexed by 'username' and 'display_name' | // Return an array of users, indexed by 'username' and 'display_name' | |||
public function getUsernames() | public function getUsernames() : array | |||
{ | { | |||
$sql = "SELECT name AS username, display_name AS display_name | $sql = "SELECT name AS username, display_name AS display_name | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
ORDER BY display_name"; | ORDER BY display_name"; | |||
$res = \MRBS\db()->query($sql); | $res = db()->query($sql); | |||
return $res->all_rows_keyed(); | $users = $res->all_rows_keyed(); | |||
// Although the users are probably already sorted, we sort them again becaus | ||||
e MRBS | ||||
// offers an option for sorting by first or last name. | ||||
self::sortUsers($users); | ||||
return $users; | ||||
} | } | |||
// Return an array of all users | // Return an array of all users | |||
public function getUsers() | public function getUsers() : array | |||
{ | { | |||
$sql = "SELECT * | $sql = "SELECT * | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
ORDER BY name"; | ORDER BY name"; | |||
$res = \MRBS\db()->query($sql); | $res = db()->query($sql); | |||
return $res->all_rows_keyed(); | return $res->all_rows_keyed(); | |||
} | } | |||
// Checks whether validation of a user by email address is possible and allowe d. | // Checks whether validation of a user by email address is possible and allowe d. | |||
public function canValidateByEmail() | public function canValidateByEmail() : bool | |||
{ | { | |||
return true; | return true; | |||
} | } | |||
// Checks whether the method has a password reset facility | // Checks whether the method has a password reset facility | |||
public function canResetPassword() | public function canResetPassword() : bool | |||
{ | { | |||
return true; | return true; | |||
} | } | |||
// Checks whether the password by reset by supplying an email address. | // Checks whether the password by reset by supplying an email address. | |||
// We allow resetting by email, even if there are multiple users with the | // We allow resetting by email, even if there are multiple users with the | |||
// same email address. | // same email address. | |||
public function canResetByEmail() | public function canResetByEmail() : bool | |||
{ | { | |||
return $this->canValidateByEmail(); | return $this->canValidateByEmail(); | |||
} | } | |||
public function requestPassword($login) | public function requestPassword(?string $login) : bool | |||
{ | { | |||
if (!isset($login) || ($login === '')) | if (!isset($login) || ($login === '')) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
// Get the possible users given this login, which could be a username or ema il address. | // Get the possible users given this login, which could be a username or ema il address. | |||
// However all the possible users must have the same email address, so check the email | // However all the possible users must have the same email address, so check the email | |||
// addresses at the same time. | // addresses at the same time. | |||
$possible_users = array(); | $possible_users = array(); | |||
skipping to change at line 222 | skipping to change at line 242 | |||
$possible_users[] = $user; | $possible_users[] = $user; | |||
} | } | |||
if ($this->canValidateByEmail()) | if ($this->canValidateByEmail()) | |||
{ | { | |||
$users = $this->getUsersByEmail($login); | $users = $this->getUsersByEmail($login); | |||
if (!empty($users)) | if (!empty($users)) | |||
{ | { | |||
// Check that the email addresses are the same | // Check that the email addresses are the same | |||
if (!empty($possible_users) && | if (!empty($possible_users) && | |||
(\MRBS\utf8_strtolower($possible_users[0]['email']) !== \MRBS\utf8_s trtolower($login))) | (utf8_strtolower($possible_users[0]['email']) !== utf8_strtolower($l ogin))) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
foreach ($users as $user) | foreach ($users as $user) | |||
{ | { | |||
$possible_users[] = $user; | $possible_users[] = $user; | |||
} | } | |||
} | } | |||
} | } | |||
if (!empty($possible_users)) | if (!empty($possible_users)) | |||
{ | { | |||
// Generate a key | // Generate a key | |||
$key = \MRBS\generate_token(32); | $key = generate_token(32); | |||
// Update the database | // Update the database | |||
if ($this->setResetKey($possible_users, $key)) | if ($this->setResetKey($possible_users, $key)) | |||
{ | { | |||
// Email the user | // Email the user | |||
return $this->notifyUser($possible_users, $key); | return $this->notifyUser($possible_users, $key); | |||
} | } | |||
} | } | |||
return false; | return false; | |||
} | } | |||
public function resetPassword($username, $key, $password) | public function resetPassword(?string $username, ?string $key, ?string $passwo rd) : bool | |||
{ | { | |||
// Check that we've got a password and we're allowed to reset the password | // Check that we've got a password and we're allowed to reset the password | |||
if (!isset($password) || !\MRBS\auth()->isValidReset($username, $key)) | if (!isset($password) || !auth()->isValidReset($username, $key)) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
// Set the new password and clear the reset key | // Set the new password and clear the reset key | |||
$sql = "UPDATE " . \MRBS\_tbl('users') . " | $sql = "UPDATE " . _tbl('users') . " | |||
SET password_hash=:password_hash, | SET password_hash=:password_hash, | |||
reset_key_hash=NULL, | reset_key_hash=NULL, | |||
reset_key_expiry=0 | reset_key_expiry=0 | |||
WHERE name=:name"; // PostgreSQL does not support LIMIT with UPDAT E | WHERE name=:name"; // PostgreSQL does not support LIMIT with UPDAT E | |||
$sql_params = array( | $sql_params = array( | |||
':password_hash' => password_hash($password, PASSWORD_DEFAULT), | ':password_hash' => password_hash($password, PASSWORD_DEFAULT), | |||
':name' => $username | ':name' => $username | |||
); | ); | |||
\MRBS\db()->command($sql, $sql_params); | db()->command($sql, $sql_params); | |||
return true; | return true; | |||
} | } | |||
public function isValidReset($user, $key) | public function isValidReset(?string $user, ?string $key) : bool | |||
{ | { | |||
if (!isset($user) || !isset($key) || ($user === '') || ($key === '')) | if (!isset($user) || !isset($key) || ($user === '') || ($key === '')) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
$sql = "SELECT reset_key_hash, reset_key_expiry | $sql = "SELECT reset_key_hash, reset_key_expiry | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
WHERE name=:name | WHERE name=:name | |||
LIMIT 1"; | LIMIT 1"; | |||
$sql_params = array(':name' => $user); | $sql_params = array(':name' => $user); | |||
$res = \MRBS\db()->query($sql,$sql_params); | $res = db()->query($sql,$sql_params); | |||
// Check we've found a row | // Check we've found a row | |||
if ($res->count() == 0) | if ($res->count() == 0) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
$row = $res->next_row_keyed(); | $row = $res->next_row_keyed(); | |||
// Check that the reset hasn't expired | // Check that the reset hasn't expired | |||
if (time() > $row['reset_key_expiry']) | if (time() > $row['reset_key_expiry']) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
// Check we've got the correct key | // Check we've got the correct key | |||
return password_verify($key, $row['reset_key_hash']); | return password_verify($key, $row['reset_key_hash']); | |||
} | } | |||
private function notifyUser(array $users, $key) | protected function getRegistrantsDisplayNamesUnsorted(int $id) : array | |||
{ | ||||
// For the 'db' auth type we can improve performance by doing a single query | ||||
// on the participants table joined with the users table. (Actually it's tw | ||||
o | ||||
// queries in a UNION: one getting the rows where there isn't an entry in th | ||||
e | ||||
// users table and another the rows where there is.) | ||||
$sql = "SELECT P.username as display_name | ||||
FROM " . _tbl('participants') . " P | ||||
LEFT JOIN " . _tbl('users') . " U | ||||
ON P.username=U.name | ||||
WHERE P.entry_id=:entry_id | ||||
AND U.name IS NULL | ||||
UNION | ||||
SELECT U.display_name | ||||
FROM " . _tbl('participants') . " P | ||||
LEFT JOIN " . _tbl('users') . " U | ||||
ON P.username=U.name | ||||
WHERE P.entry_id=:entry_id | ||||
AND U.name IS NOT NULL"; | ||||
return db()->query_array($sql, array(':entry_id' => $id)); | ||||
} | ||||
private function notifyUser(array $users, string $key) : bool | ||||
{ | { | |||
global $auth, $mail_settings; | global $auth, $mail_settings; | |||
if (empty($users) || !isset($users[0]['email']) || ($users[0]['email'] === ' ')) | if (empty($users) || !isset($users[0]['email']) || ($users[0]['email'] === ' ')) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
$expiry_time = $auth['db']['reset_key_expiry']; | $expiry_time = $auth['db']['reset_key_expiry']; | |||
\MRBS\toTimeString($expiry_time, $expiry_units, true, 'hours'); | toTimeString($expiry_time, $expiry_units, true, 'hours'); | |||
$addresses = array( | $addresses = array( | |||
'from' => $mail_settings['from'] | 'from' => $mail_settings['from'] | |||
); | ); | |||
// Add the To address, using the display name if possible (ie if it exists a nd there's | // Add the To address, using the display name if possible (ie if it exists a nd there's | |||
// only one user). | // only one user). | |||
// Also get a name to use in the message body | // Also get a name to use in the message body | |||
if ((count($users) == 1) && | if ((count($users) == 1) && | |||
isset($users[0]['display_name']) && | isset($users[0]['display_name']) && | |||
($users[0]['display_name'] !== '')) | ($users[0]['display_name'] !== '')) | |||
{ | { | |||
$mailer = new PHPMailer(); | $mailer = new PHPMailer(); | |||
$mailer->CharSet = \MRBS\get_mail_charset(); | $mailer->CharSet = get_mail_charset(); | |||
// Note that addrFormat() returns a MIME-encoded address | // Note that addrFormat() returns a MIME-encoded address | |||
$addresses['to'] = $mailer->addrFormat(array($users[0]['email'], $users[0] ['display_name'])); | $addresses['to'] = $mailer->addrFormat(array($users[0]['email'], $users[0] ['display_name'])); | |||
$name = $users[0]['display_name']; | $name = $users[0]['display_name']; | |||
} | } | |||
else | else | |||
{ | { | |||
$addresses['to'] = $users[0]['email']; | $addresses['to'] = $users[0]['email']; | |||
// If there's only one user we can use the username, otherwise we have to use the | // If there's only one user we can use the username, otherwise we have to use the | |||
// email address which is the same for all users. | // email address which is the same for all users. | |||
$name = (count($users) == 1) ? $users[0]['name'] : $users[0]['email']; | $name = (count($users) == 1) ? $users[0]['name'] : $users[0]['email']; | |||
} | } | |||
$subject = \MRBS\get_vocab('password_reset_subject'); | $subject = get_vocab('password_reset_subject'); | |||
$body = '<p>'; | $body = '<p>'; | |||
$body .= \MRBS\get_vocab('password_reset_body', intval($expiry_time), $expir y_units, $name); | $body .= get_vocab('password_reset_body', intval($expiry_time), $expiry_unit s, $name); | |||
$body .= "</p>\n"; | $body .= "</p>\n"; | |||
// Construct and add in the link | // Construct and add in the link | |||
$usernames = array(); | $usernames = array(); | |||
foreach ($users as $user) | foreach ($users as $user) | |||
{ | { | |||
$usernames[] = $user['name']; | $usernames[] = $user['name']; | |||
} | } | |||
$usernames = array_unique($usernames); | $usernames = array_unique($usernames); | |||
$vars = array( | $vars = array( | |||
'action' => 'reset', | 'action' => 'reset', | |||
'usernames' => $usernames, | 'usernames' => $usernames, | |||
'key' => $key | 'key' => $key | |||
); | ); | |||
$query = http_build_query($vars, '', '&'); | $query = http_build_query($vars, '', '&'); | |||
$href = (\MRBS\is_https()) ? 'https' : 'http'; | $href = url_base() . multisite("reset_password.php?$query"); | |||
$href .= '://' . \MRBS\url_base() . \MRBS\multisite("reset_password.php?$que | $body .= "<p><a href=\"$href\">" . get_vocab('reset_password') . "</a>.</p>" | |||
ry"); | ; | |||
$body .= "<p><a href=\"$href\">" . \MRBS\get_vocab('reset_password') . "</a> | ||||
.</p>"; | ||||
MailQueue::add( | MailQueue::add( | |||
$addresses, | $addresses, | |||
$subject, | $subject, | |||
array('content' => strip_tags($body)), | array('content' => strip_tags($body)), | |||
array('content' => $body, | array('content' => $body, | |||
'cid' => \MRBS\generate_global_uid("html")), | 'cid' => generate_global_uid("html")), | |||
null, | null, | |||
\MRBS\get_mail_charset() | get_mail_charset() | |||
); | ); | |||
return true; | return true; | |||
} | } | |||
private function setResetKey(array $users, $key) | private function setResetKey(array $users, string $key) : bool | |||
{ | { | |||
global $auth; | global $auth; | |||
if (empty($users)) | if (empty($users)) | |||
{ | { | |||
return false; | return false; | |||
} | } | |||
$ids = array(); | $ids = array(); | |||
foreach($users as $user) | foreach($users as $user) | |||
{ | { | |||
// Use intval to make sure the string is safe for the SQL query | // Use intval to make sure the string is safe for the SQL query | |||
$ids[] = intval($user['id']); | $ids[] = intval($user['id']); | |||
} | } | |||
$sql = "UPDATE " . \MRBS\_tbl('users') . " | $sql = "UPDATE " . _tbl('users') . " | |||
SET reset_key_hash=:reset_key_hash, | SET reset_key_hash=:reset_key_hash, | |||
reset_key_expiry=:reset_key_expiry | reset_key_expiry=:reset_key_expiry | |||
WHERE id IN (" . implode(',', $ids) . ")"; | WHERE id IN (" . implode(',', $ids) . ")"; | |||
$sql_params = array( | $sql_params = array( | |||
':reset_key_hash' => password_hash($key, PASSWORD_DEFAULT), | ':reset_key_hash' => password_hash($key, PASSWORD_DEFAULT), | |||
':reset_key_expiry' => time() + $auth['db']['reset_key_expiry'] | ':reset_key_expiry' => time() + $auth['db']['reset_key_expiry'] | |||
); | ); | |||
\MRBS\db()->command($sql, $sql_params); | db()->command($sql, $sql_params); | |||
return true; | return true; | |||
} | } | |||
private function getUserByUsername($username) | private function getUserByUsername(string $username) : ?array | |||
{ | { | |||
$sql = "SELECT * | $sql = "SELECT * | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
WHERE name=:name | WHERE name=:name | |||
LIMIT 1"; | LIMIT 1"; | |||
$result = \MRBS\db()->query($sql, array(':name' => $username)); | $result = db()->query($sql, array(':name' => $username)); | |||
// The username doesn't exist - return NULL | // The username doesn't exist - return NULL | |||
if ($result->count() === 0) | if ($result->count() === 0) | |||
{ | { | |||
return null; | return null; | |||
} | } | |||
return $result->next_row_keyed(); | return $result->next_row_keyed(); | |||
} | } | |||
private function getUserByUserId($id) | private function getUserByUserId(int $id) : ?array | |||
{ | { | |||
$sql = "SELECT * | $sql = "SELECT * | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
WHERE id=:id | WHERE id=:id | |||
LIMIT 1"; | LIMIT 1"; | |||
$result = \MRBS\db()->query($sql, array(':id' => $id)); | $result = db()->query($sql, array(':id' => $id)); | |||
// The username doesn't exist - return NULL | // The username doesn't exist - return NULL | |||
if ($result->count() === 0) | if ($result->count() === 0) | |||
{ | { | |||
return null; | return null; | |||
} | } | |||
return $result->next_row_keyed(); | return $result->next_row_keyed(); | |||
} | } | |||
// Returns an array of rows for all users with the email address $email. | // Returns an array of rows for all users with the email address $email. | |||
// Assumes that email addresses are case insensitive. | // Assumes that email addresses are case insensitive. | |||
// Allows equivalent Gmail addresses, ie ignores dots in the local part and | // Allows equivalent Gmail addresses, ie ignores dots in the local part and | |||
// treats gmail.com and googlemail.com as equivalent domains. | // treats gmail.com and googlemail.com as equivalent domains. | |||
private function getUsersByEmail($email) | private function getUsersByEmail(string $email) : array | |||
{ | { | |||
global $auth; | global $auth; | |||
$result = array(); | $result = array(); | |||
// For the moment we will assume that email addresses are case insensitive. Whilst it is true | // For the moment we will assume that email addresses are case insensitive. Whilst it is true | |||
// on most systems, it isn't always true. The domain is case insensitive bu t the local-part can | // on most systems, it isn't always true. The domain is case insensitive bu t the local-part can | |||
// be case sensitive. But before we can take account of this, the email add resses in the database | // be case sensitive. But before we can take account of this, the email add resses in the database | |||
// need to be normalised so that all the domain names are stored in lower ca se. Then it will be | // need to be normalised so that all the domain names are stored in lower ca se. Then it will be | |||
// possible to do a case sensitive comparison. | // possible to do a case sensitive comparison. | |||
if (\MRBS\utf8_strpos($email, '@') === false) | if (utf8_strpos($email, '@') === false) | |||
{ | { | |||
if (!empty($auth['allow_local_part_email'])) | if (!empty($auth['allow_local_part_email'])) | |||
{ | { | |||
// We're just checking the local-part of the email address | // We're just checking the local-part of the email address | |||
$sql_params = array($email); | $sql_params = array($email); | |||
$condition = "LOWER(?)=LOWER(" . \MRBS\db()->syntax_simple_split('email' , '@', 1, $sql_params) .")"; | $condition = "LOWER(?)=LOWER(" . db()->syntax_simple_split('email', '@', 1, $sql_params) .")"; | |||
} | } | |||
else | else | |||
{ | { | |||
return $result; | return $result; | |||
} | } | |||
} | } | |||
else | else | |||
{ | { | |||
$address = \MRBS\parse_email($email); | $address = parse_email($email); | |||
// Invalid email address | // Invalid email address | |||
if ($address === false) | if ($address === false) | |||
{ | { | |||
return $result; | return $result; | |||
} | } | |||
// Special case for Gmail addresses: ignore dots in the local part and tre at gmail.com and | // Special case for Gmail addresses: ignore dots in the local part and tre at gmail.com and | |||
// googlemail.com as equivalent domains. | // googlemail.com as equivalent domains. | |||
elseif (in_array(\MRBS\utf8_strtolower($address['domain']), array('gmail.c om', 'googlemail.com'))) | elseif (in_array(utf8_strtolower($address['domain']), array('gmail.com', ' googlemail.com'))) | |||
{ | { | |||
$sql_params = array(str_replace('.', '', $address['local'])); | $sql_params = array(str_replace('.', '', $address['local'])); | |||
$sql_params[] = $sql_params[0]; | $sql_params[] = $sql_params[0]; | |||
$condition = "(LOWER(?) = REPLACE(TRIM(TRAILING '@gmail.com' FROM LOWER( email)), '.', '')) OR " . | $condition = "(LOWER(?) = REPLACE(TRIM(TRAILING '@gmail.com' FROM LOWER( email)), '.', '')) OR " . | |||
"(LOWER(?) = REPLACE(TRIM(TRAILING '@googlemail.com' FROM L OWER(email)), '.', ''))"; | "(LOWER(?) = REPLACE(TRIM(TRAILING '@googlemail.com' FROM L OWER(email)), '.', ''))"; | |||
} | } | |||
// Everything else: check the complete email address | // Everything else: check the complete email address | |||
else | else | |||
{ | { | |||
$sql_params = array($email); | $sql_params = array($email); | |||
$condition = "LOWER(?)=LOWER(email)"; | $condition = "LOWER(?)=LOWER(email)"; | |||
} | } | |||
} | } | |||
$sql = "SELECT * | $sql = "SELECT * | |||
FROM " . \MRBS\_tbl('users') . " | FROM " . _tbl('users') . " | |||
WHERE $condition"; | WHERE $condition"; | |||
$res = \MRBS\db()->query($sql, $sql_params); | $res = db()->query($sql, $sql_params); | |||
while (false !== ($row = $res->next_row_keyed())) | while (false !== ($row = $res->next_row_keyed())) | |||
{ | { | |||
$result[] = $row; | $result[] = $row; | |||
} | } | |||
return $result; | return $result; | |||
} | } | |||
private function rehash($password, $column_name, $column_value) | private function rehash(string $password, string $column_name, string $column_ value) : void | |||
{ | { | |||
$sql_params = array(password_hash($password, PASSWORD_DEFAULT)); | $sql_params = array(password_hash($password, PASSWORD_DEFAULT)); | |||
switch ($column_name) | switch ($column_name) | |||
{ | { | |||
case 'name': | case 'name': | |||
$condition = \MRBS\db()->syntax_casesensitive_equals($column_name, \MRBS \utf8_strtolower($column_value), $sql_params); | $condition = db()->syntax_casesensitive_equals($column_name, utf8_strtol ower($column_value), $sql_params); | |||
break; | break; | |||
case 'email': | case 'email': | |||
// For the moment we will assume that email addresses are case insensiti ve. Whilst it is true | // For the moment we will assume that email addresses are case insensiti ve. Whilst it is true | |||
// on most systems, it isn't always true. The domain is case insensitiv e but the local-part can | // on most systems, it isn't always true. The domain is case insensitiv e but the local-part can | |||
// be case sensitive. But before we can take account of this, the emai l addresses in the database | // be case sensitive. But before we can take account of this, the emai l addresses in the database | |||
// need to be normalised so that all the domain names are stored in lowe r case. Then it will be possible | // need to be normalised so that all the domain names are stored in lowe r case. Then it will be possible | |||
// to do a case sensitive comparison. | // to do a case sensitive comparison. | |||
$sql_params[] = $column_value; | $sql_params[] = $column_value; | |||
$condition = "LOWER($column_name)=LOWER(?)"; | $condition = "LOWER($column_name)=LOWER(?)"; | |||
break; | break; | |||
default: | default: | |||
trigger_error("Unsupported column name '$column_name'.", E_USER_NOTICE); | trigger_error("Unsupported column name '$column_name'.", E_USER_NOTICE); | |||
return; | return; | |||
break; | break; | |||
} | } | |||
$sql = "UPDATE " . \MRBS\_tbl('users') . " | $sql = "UPDATE " . _tbl('users') . " | |||
SET password_hash=? | SET password_hash=? | |||
WHERE $condition"; | WHERE $condition"; | |||
\MRBS\db()->command($sql, $sql_params); | db()->command($sql, $sql_params); | |||
} | } | |||
// Checks $password against $password_hash for the row in the user table | // Checks $password against $password_hash for the row in the user table | |||
// where $column_name=$column_value. Typically $column_name will be either | // where $column_name=$column_value. Typically $column_name will be either | |||
// 'name' or 'email'. | // 'name' or 'email'. | |||
// Returns a boolean: true if they match, otherwise false. | // Returns a boolean: true if they match, otherwise false. | |||
private function checkPassword($password, $password_hash, $column_name, $colum n_value) | private function checkPassword(string $password, string $password_hash, string $column_name, string $column_value) : bool | |||
{ | { | |||
$result = false; | $result = false; | |||
$do_rehash = false; | $do_rehash = false; | |||
/* If the hash starts '$' it's a PHP password hash */ | /* If the hash starts '$' it's a PHP password hash */ | |||
if (substr($password_hash, 0, 1) == '$') | if (substr($password_hash, 0, 1) == '$') | |||
{ | { | |||
if (password_verify($password, $password_hash)) | if (password_verify($password, $password_hash)) | |||
{ | { | |||
$result = true; | $result = true; | |||
End of changes. 59 change blocks. | ||||
64 lines changed or deleted | 108 lines changed or added |