diff options
| author | Greg Roach <fisharebest@gmail.com> | 2014-05-06 17:33:46 +0100 |
|---|---|---|
| committer | Greg Roach <fisharebest@gmail.com> | 2014-05-06 17:33:46 +0100 |
| commit | 04e895d26b4a0f606b8f83488ecb32127df081cf (patch) | |
| tree | 19336b0bcb1458be680c51437eb69d7cfe1c2492 /library/WT/User.php | |
| parent | 7910461c5ea42dcfeeaa80c5820d949c5dabe9de (diff) | |
| download | webtrees-04e895d26b4a0f606b8f83488ecb32127df081cf.tar.gz webtrees-04e895d26b4a0f606b8f83488ecb32127df081cf.tar.bz2 webtrees-04e895d26b4a0f606b8f83488ecb32127df081cf.zip | |
Replace global user functions with WT_User class
Diffstat (limited to 'library/WT/User.php')
| -rw-r--r-- | library/WT/User.php | 453 |
1 files changed, 341 insertions, 112 deletions
diff --git a/library/WT/User.php b/library/WT/User.php index 1f095e5e45..30d47fbaed 100644 --- a/library/WT/User.php +++ b/library/WT/User.php @@ -25,14 +25,15 @@ if (!defined('WT_WEBTREES')) { class WT_User { // Reasons why a user authentication attempt failed - const ACCOUNT_NOT_VERIFIED = 'ACCOUNT_NOT_VERIFIED'; const ACCOUNT_NOT_APPROVED = 'ACCOUNT_NOT_APPROVED'; + const ACCOUNT_NOT_VERIFIED = 'ACCOUNT_NOT_VERIFIED'; const INCORRECT_PASSWORD = 'INCORRECT_PASSWORD'; - const NO_SUCH_USER = 'NO_SUCH_USER'; const NO_SESSION_COOKIES = 'NO_SESSION_COOKIES'; + const NO_SUCH_USER = 'NO_SUCH_USER'; // Reasons why a user account cannot be created - const DUPLICATE_USER_NAME = 'DUPLICATE_USER_NAME'; const DUPLICATE_EMAIL = 'DUPLICATE_EMAIL'; + const DUPLICATE_USER_NAME = 'DUPLICATE_USER_NAME'; + const PASSWORD_MISMATCH = 'PASSWORD_MISMATCH'; // Attributes of the user, from the wt_user table private $user_id; @@ -40,12 +41,17 @@ class WT_User { private $real_name; private $email; - // Settings for the user, from the wt_user_setting table + /** @var array $settings Settings for the user, from the wt_user_setting table */ private $settings; // The current user private static $current_user; + /** + * Who is the currently logged in user? + * + * @return WT_User + */ public static function currentUser() { global $WT_SESSION; @@ -56,48 +62,173 @@ class WT_User { return self::$current_user; } - public static function isLoggedIn() { - return self::currentUser()->getUserId() !== null; + /** + * Is a user currently logged in? + * + * @return bool + */ + public function isLoggedIn() { + global $WT_SESSION; + + return $this->getUserId() !== null && $this->getUserId() === $WT_SESSION->wt_user; + } + + + /** Authenticate a username/password combination. + * + * @param string $identity Username or email address + * @param string $password + * + * @return WT_User + * @throws Exception + * + */ + public static function login($identity, $password) { + global $WT_SESSION; + + self::passwordCompatibility(); // For PHP <= 5.4 + + // If no cookies are available, then we cannot log in. + if (empty($_COOKIE)) { + throw new Exception(self::NO_SESSION_COOKIES); + } + + $row = WT_DB::prepare( + "SELECT SQL_CACHE user_id, password FROM `wt_user` WHERE user_name = ? OR email = ?" + )->execute(array($identity, $identity))->fetchOneRow(); + + if (!$row) { + // No such user with that username or email address + throw new Exception(self::NO_SUCH_USER); + } + + $user = new WT_User($row->user_id); + if (!password_verify($password, $row->password)) { + // Incorrect password + throw new Exception(self::INCORRECT_PASSWORD); + } + + // Was the password hash created using an old or insecure algorithm? + if (password_needs_rehash($row->password, PASSWORD_DEFAULT)) { + // Generate a new hash + $user->setPassword($row->password); + } + + if (!$user->getSetting('verified') && !$user->getSetting('canadmin')) { + // The user has not verified their email address. (Administrators do not need to be approved.) + throw new Exception(self::ACCOUNT_NOT_VERIFIED); + } + + if (!$user->getSetting('verified_by_admin') && !$user->getSetting('canadmin')) { + // An administrator has not approved the account. (Administrators do not need to be approved.) + throw new Exception(self::ACCOUNT_NOT_APPROVED); + } + + // All checks passed. The user is permitted to log in. + $WT_SESSION->wt_user = $user->getUserId(); + + // Whenever we change our authorization level, change the session ID. + Zend_Session::regenerateId(); + + return $user; + } + + /** + * End the session for the current user + * + * @return void + */ + public static function logout() { + if (WT_User::currentUser()->isLoggedIn()) { + self::$current_user = null; + Zend_Session::destroy(); + } } - public static function isAdmin() { - return self::currentUser()->getSetting('canadmin'); + /** + * Are we currently logged in as an administrator? + * + * @return bool + */ + public function isAdmin() { + return $this->getSetting('canadmin'); } - public static function isManager(WT_Tree $tree) { - return self::isAdmin() || $tree->userPreference(self::currentUser(), 'canedit') === 'admin'; + /** + * Are we logged in as a manager of a particular tree? + * + * @param WT_Tree $tree + * + * @return bool + */ + public function isManager(WT_Tree $tree) { + return $this->isAdmin() || $tree->userPreference($this->currentUser()->getUserId(), 'canedit') === 'admin'; } - public static function isModerator(WT_Tree $tree) { - return self::isManager($tree) || $tree->userPreference(self::currentUser(), 'canedit') === 'accept'; + /** + * Are we logged in as a moderator of a particular tree? + * + * @param WT_Tree $tree + * + * @return bool + */ + public function isModerator(WT_Tree $tree) { + return $this->isManager($tree) || $tree->userPreference($this->currentUser()->getUserId(), 'canedit') === 'accept'; } - public static function isEditor(WT_Tree $tree) { - return self::isModerator($tree) || $tree->userPreference(self::currentUser(), 'canedit') === 'edit'; + /** + * Are we logged in as an editor of a particular tree? + * + * @param WT_Tree $tree + * + * @return bool + */ + public function isEditor(WT_Tree $tree) { + return $this->isModerator($tree) || $tree->userPreference($this->currentUser()->getUserId(), 'canedit') === 'edit'; } - public static function isMember(WT_Tree $tree) { - return self::isEditor($tree) || $tree->userPreference(self::currentUser(), 'canedit') === 'access'; + /** + * Are we logged in as a member of a particular tree? + * + * @param WT_Tree $tree + * + * @return bool + */ + public function isMember(WT_Tree $tree) { + return $this->isEditor($tree) || $tree->userPreference($this->currentUser()->getUserId(), 'canedit') === 'access'; } - // Create a new user. - // - // On success, return a new user object. - // On failure, throw an exception. - public static function create($user_name, $real_name, $email, $password) { - self::passwordCompatibility(); + /** + * Create a new user. + * + * @param string $user_name + * @param string $real_name + * @param string $email + * @param string $password1 + * @param string $password2 + * + * @return WT_User + * + * @throws Exception + */ + public static function create($user_name, $real_name, $email, $password1, $password2) { + self::passwordCompatibility(); // For PHP <= 5.4 + + if ($password1 !== $password2) { + throw new Exception(self::PASSWORD_MISMATCH); + } - if (WT_DB::prepare("SELECT 1 FROM `##user` WHERE user_name = ?")->execute(array($user_name))->fetchOne()) { - throw new exception(self::DUPLICATE_USER_NAME); + if (WT_User::userNameExists($user_name)) { + throw new Exception(self::DUPLICATE_USER_NAME); } - if (WT_DB::prepare("SELECT 1 FROM `##user` WHERE email = ?")->execute(array($email))->fetchOne()) { - throw new exception(self::DUPLICATE_EMAIL); + if (WT_User::emailExists($email)) { + throw new Exception(self::DUPLICATE_EMAIL); } WT_DB::prepare( "INSERT INTO `##user` (user_name, real_name, email, password) VALUES (?, ?, ?, ?)" - )->execute(array($user_name, $real_name, $email, password_hash($password, PASSWORD_DEFAULT))); + )->execute(array($user_name, $real_name, $email, password_hash($password1, PASSWORD_DEFAULT))); $user = new WT_User(); $user->user_id = WT_DB::prepare("SELECT LAST_INSERT_ID()")->fetchOne(); @@ -108,36 +239,149 @@ class WT_User { return $user; } - // Create a new user object from a row in the database - public function __construct($user_id=null) { + /** + * Get a list of all users. + * + * @return array + */ + public static function getAll() { + $users = array(); + + $rows = WT_DB::prepare( + "SELECT SQL_CACHE user_id, user_name, real_name, email" . + " FROM `##user`" . + " WHERE user_id > 0" . + " ORDER BY user_name" + )->fetchAll(); + + foreach ($rows as $row) { + $users[] = new WT_User($row->user_id, $row->user_name, $row->real_name, $row->email); + } + + return $users; + } + + /** + * Get a list of all administrators. + * + * @return array + */ + public static function getAdmins() { + $users = array(); + + $rows = WT_DB::prepare( + "SELECT SQL_CACHE user_id, user_name, real_name, email" . + " FROM `##user`" . + " JOIN `##user_setting` USING (user_id)" . + " WHERE user_id > 0" . + " AND setting_name = 'canadmin'" . + " AND setting_value = '1'" . + " ORDER BY user_name" + )->fetchAll(); + + foreach ($rows as $row) { + $users[] = new WT_User($row->user_id, $row->user_name, $row->real_name, $row->email); + } + + return $users; + } + + /** + * Check whether a username is already used by an existing user. + * + * @param string $user_name + * + * @return bool + */ + public static function userNameExists($user_name) { + return WT_DB::prepare( + "SELECT SQL_CACHE 1 FROM `##user` WHERE user_name = ?" + )->execute(array($user_name))->fetchOne() !== null; + } + + /** + * Check whether an email address is already used by an existing user. + * + * @param string $email + * + * @return bool + */ + public static function emailExists($email) { + return WT_DB::prepare( + "SELECT SQL_CACHE 1 FROM `##user` WHERE email = ?" + )->execute(array($email))->fetchOne() !== null; + } + + /** + * Create a new user object from a row in the database. + * + * @param integer|null $user_id + */ + public function __construct($user_id = null, $user_name = null, $real_name = null, $email = null) { if ($user_id !== null) { - $row = WT_DB::prepare( - "SELECT SQL_CACHE user_name, real_name, email FROM `##user` WHERE user_id = ?" - )->execute(array($user_id))->fetchOneRow(); + if ($user_name === null) { + $row = WT_DB::prepare( + "SELECT SQL_CACHE user_name, real_name, email FROM `##user` WHERE user_id = ?" + )->execute(array($user_id))->fetchOneRow(); - if ($row) { + if ($row) { + $this->user_id = $user_id; + $this->user_name = $row->user_name; + $this->real_name = $row->real_name; + $this->email = $row->email; + } + } else { $this->user_id = $user_id; - $this->user_name = $row->user_name; - $this->real_name = $row->real_name; - $this->email = $row->email; + $this->user_name = $user_name; + $this->real_name = $real_name; + $this->email = $email; } } } + /** + * Delete a user + * + * @param $user_id + */ + function delete() { + // Don't delete the logs. + WT_DB::prepare("UPDATE `##log` SET user_id=NULL WHERE user_id =?")->execute(array($this->getUserId())); + // Take over the user’s pending changes. + // TODO: perhaps we should prevent deletion of users with pending changes? + WT_DB::prepare("DELETE FROM `##change` WHERE user_id=? AND status='accepted'")->execute(array($this->getUserId())); + WT_DB::prepare("UPDATE `##change` SET user_id=? WHERE user_id=?")->execute(array(WT_USER_ID, $this->getUserId())); + + WT_DB::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE user_id=?")->execute(array($this->getUserId())); + WT_DB::prepare("DELETE FROM `##block` WHERE user_id=?" )->execute(array($this->getUserId())); + WT_DB::prepare("DELETE FROM `##user_gedcom_setting` WHERE user_id=?" )->execute(array($this->getUserId())); + WT_DB::prepare("DELETE FROM `##user_setting` WHERE user_id=?" )->execute(array($this->getUserId())); + WT_DB::prepare("DELETE FROM `##message` WHERE user_id=?" )->execute(array($this->getUserId())); + WT_DB::prepare("DELETE FROM `##user` WHERE user_id=?" )->execute(array($this->getUserId())); + } + // Getters and setters for user attributes public function getUserId() { return $this->user_id; } + private function setUserId($user_id) { + $this->user_id = $user_id; + } + public function getUserName() { return $this->user_name; } public function setUserName($user_name) { - $this->user_name = $user_name; - WT_DB::prepare( - "UPDATE `##user` SET user_name = ? WHERE user_id = ?" - )->execute(array($user_name, $this->user_id)); + if ($this->user_name !== $user_name) { + $this->user_name = $user_name; + WT_DB::prepare( + "UPDATE `##user` SET user_name = ? WHERE user_id = ?" + )->execute(array($user_name, $this->user_id)); + } + + return $this; } public function getRealName() { @@ -145,10 +389,14 @@ class WT_User { } public function setRealName($real_name) { - $this->real_name = $real_name; - WT_DB::prepare( - "UPDATE `##user` SET real_name = ? WHERE user_id = ?" - )->execute(array($real_name, $this->user_id)); + if ($this->real_name !== $real_name) { + $this->real_name = $real_name; + WT_DB::prepare( + "UPDATE `##user` SET real_name = ? WHERE user_id = ?" + )->execute(array($real_name, $this->user_id)); + } + + return $this; } public function getEmail() { @@ -156,27 +404,40 @@ class WT_User { } public function setEmail($email) { - $this->email = $email; - WT_DB::prepare( - "UPDATE `##user` SET email = ? WHERE user_id = ?" - )->execute(array($email, $this->user_id)); + if ($this->email !== $email) { + $this->email = $email; + WT_DB::prepare( + "UPDATE `##user` SET email = ? WHERE user_id = ?" + )->execute(array($email, $this->user_id)); + } + + return $this; } public function setPassword($password) { - self::passwordCompatibility(); + self::passwordCompatibility(); // For PHP <= 5.4 WT_DB::prepare( "UPDATE `##user` SET password = ? WHERE user_id = ?" )->execute(array(password_hash($password, PASSWORD_DEFAULT), $this->user_id)); + + return $this; } - // Fetch a user option/setting from the wt_user_setting table - // - // Since we'll fetch several settings for each user, and since there aren’t - // that many of them, fetch them all in one database query - public function getSetting($setting_name, $default=null) { + /** + * Fetch a user option/setting from the wt_user_setting table + * + * Since we'll fetch several settings for each user, and since there aren’t + * that many of them, fetch them all in one database query + * + * @param string $setting_name + * @param string|null $default + * + * @return string + */ + public function getSetting($setting_name, $default = null) { if ($this->settings === null) { - if ($this->isLoggedIn()) { + if ($this->getUserId()) { $this->settings = WT_DB::prepare( "SELECT SQL_CACHE setting_name, setting_value FROM `##user_setting` WHERE user_id = ?" )->execute(array($this->user_id))->fetchAssoc(); @@ -192,21 +453,34 @@ class WT_User { } } - // Update a setting for the user. + /** + * Update a setting for the user. + * + * @param string $setting_name + * @param string $setting_value + * + * @return WT_User + */ public function setSetting($setting_name, $setting_value) { - if ($setting_value===null) { + if ($setting_value === null) { WT_DB::prepare("DELETE FROM `##user_setting` WHERE user_id=? AND setting_name=?") ->execute(array($this->user_id, $setting_name)); unset($this->settings[$setting_name]); - } else { + } elseif ($this->settings[$setting_name] !== $setting_value) { WT_DB::prepare("REPLACE INTO `##user_setting` (user_id, setting_name, setting_value) VALUES (?, ?, LEFT(?, 255))") ->execute(array($this->user_id, $setting_name, $setting_value)); $this->settings[$setting_name] = $setting_value; } + + return $this; } - // PHP5.5 provides new password hash functions. - // For earlier versions, use a compatibility library. + /** + * Allow old versions of PHP to use the new password functions + * + * PHP5.5 provides new password hash functions. + * For earlier versions, use a compatibility library. + */ private static function passwordCompatibility() { if (!function_exists('password_hash')) { // The compatibility library requires the $2$y salt prefix, which is available in @@ -216,70 +490,25 @@ class WT_User { require 'library/ircmaxell/password-compat/lib/password.php'; } else { // For older/unpatched versions of PHP, use the default crypt behaviour. - function password_hash($password, $algo, $options=array()) { + function password_hash($password) { $salt = '$2a$12$'; $salt_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./'; for ($i = 0; $i < 22; ++$i) { - $salt .= setsubstr($salt_chars, mt_rand(0, 63), 1); + $salt .= substr($salt_chars, mt_rand(0, 63), 1); } return crypt($password, $salt); } - function password_needs_rehash($password, $algo, $options=array()) { + + function password_needs_rehash() { return false; } + function password_verify($password, $hash) { return crypt($password, $hash) === $hash; } + define('PASSWORD_DEFAULT', 1); } } } - - // Authenticate a username/password combination. - // - // On success, return the user_id of the account. - // On failure, return the reason for failure. - public static function authenticate($user_name, $password) { - self::passwordCompatibility(); - - // If no cookies are available, then we cannot log in. - if (empty($_COOKIE)) { - return self::NO_SESSION_COOKIES; - } - - $row = WT_DB::prepare( - "SELECT SQL_CACHE user_id, password FROM `wt_user` WHERE user_name = ? OR email = ?" - )->execute(array($user_name, $user_name))->fetchOneRow(); - - if (!$row) { - // No such user with that username or email address - return self::NO_SUCH_USER; - } - - $user = new WT_User($row->user_id); - if (!password_verify($password, $row->password)) { - // Incorrect password - return self::INCORRECT_PASSWORD; - } - - // Was the password hash created using an old or insecure algorithm? - if (password_needs_rehash($row->password, PASSWORD_DEFAULT)) { - // Generate a new hash - $user->setPassword(password_hash($password, PASSWORD_DEFAULT)); - } - - if (!$user->getSetting('verified') && !$user->getSetting('canadmin')) { - // The user has not verified their email address. - return self::ACCOUNT_NOT_VERIFIED; - } - - if (!$user->getSetting('verified_by_admin') && !$user->getSetting('canadmin')) { - // An administrator has not approved the account. - // Admins do not need to be approved. - return self::ACCOUNT_NOT_APPROVED; - } - - // All checks passed. The user is permitted to log in. - return $row->user_id; - } -} +}
\ No newline at end of file |
