core = &$core; $this->con = &$core->con; $this->blog_table = $core->prefix . 'blog'; $this->user_table = $core->prefix . 'user'; $this->perm_table = $core->prefix . 'permissions'; $this->perm_types = array( 'admin' => __('administrator'), 'usage' => __('manage their own entries and comments'), 'publish' => __('publish entries and comments'), 'delete' => __('delete entries and comments'), 'contentadmin' => __('manage all entries and comments'), 'categories' => __('manage categories'), 'media' => __('manage their own media items'), 'media_admin' => __('manage all media items') ); } /// @name Credentials and user permissions //@{ /** * Checks if user exists and can log in. $pwd argument is optionnal * while you may need to check user without password. This method will create * credentials and populate all needed object properties. * * @param string $user_id User ID * @param string $pwd User password * @param string $user_key User key check * @param boolean $check_blog checks if user is associated to a blog or not. * @return boolean */ public function checkUser($user_id, $pwd = null, $user_key = null, $check_blog = true) { # Check user and password $strReq = 'SELECT user_id, user_super, user_pwd, user_change_pwd, ' . 'user_name, user_firstname, user_displayname, user_email, ' . 'user_url, user_default_blog, user_options, ' . 'user_lang, user_tz, user_post_status, user_creadt, user_upddt ' . 'FROM ' . $this->con->escapeSystem($this->user_table) . ' ' . "WHERE user_id = '" . $this->con->escape($user_id) . "' "; try { $rs = $this->con->select($strReq); } catch (Exception $e) { $err = $e->getMessage(); return false; } if ($rs->isEmpty()) { sleep(rand(2, 5)); return false; } $rs->extend('rsExtUser'); if ($pwd != '') { $rehash = false; if (password_verify($pwd, $rs->user_pwd)) { // User password ok if (password_needs_rehash($rs->user_pwd, PASSWORD_DEFAULT)) { $rs->user_pwd = $this->crypt($pwd); $rehash = true; } } else { // Check if pwd still stored in old fashion way $ret = password_get_info($rs->user_pwd); if (is_array($ret) && isset($ret['algo']) && $ret['algo'] == 0) { // hash not done with password_hash() function, check by old fashion way if (crypt::hmac(DC_MASTER_KEY, $pwd, DC_CRYPT_ALGO) == $rs->user_pwd) { // Password Ok, need to store it in new fashion way $rs->user_pwd = $this->crypt($pwd); $rehash = true; } else { // Password KO sleep(rand(2, 5)); return false; } } else { // Password KO sleep(rand(2, 5)); return false; } } if ($rehash) { // Store new hash in DB $cur = $this->con->openCursor($this->user_table); $cur->user_pwd = (string) $rs->user_pwd; $cur->update("WHERE user_id = '" . $rs->user_id . "'"); } } elseif ($user_key != '') { if (http::browserUID(DC_MASTER_KEY . $rs->user_id . $this->cryptLegacy($rs->user_id)) != $user_key) { return false; } } $this->user_id = $rs->user_id; $this->user_change_pwd = (boolean) $rs->user_change_pwd; $this->user_admin = (boolean) $rs->user_super; $this->user_info['user_pwd'] = $rs->user_pwd; $this->user_info['user_name'] = $rs->user_name; $this->user_info['user_firstname'] = $rs->user_firstname; $this->user_info['user_displayname'] = $rs->user_displayname; $this->user_info['user_email'] = $rs->user_email; $this->user_info['user_url'] = $rs->user_url; $this->user_info['user_default_blog'] = $rs->user_default_blog; $this->user_info['user_lang'] = $rs->user_lang; $this->user_info['user_tz'] = $rs->user_tz; $this->user_info['user_post_status'] = $rs->user_post_status; $this->user_info['user_creadt'] = $rs->user_creadt; $this->user_info['user_upddt'] = $rs->user_upddt; $this->user_info['user_cn'] = dcUtils::getUserCN($rs->user_id, $rs->user_name, $rs->user_firstname, $rs->user_displayname); $this->user_options = array_merge($this->core->userDefaults(), $rs->options()); $this->user_prefs = new dcPrefs($this->core, $this->user_id); # Get permissions on blogs if ($check_blog && ($this->findUserBlog() === false)) { return false; } return true; } /** * This method crypt given string (password, session_id, …). * * @param string $pwd string to be crypted * @return string crypted value */ public function crypt($pwd) { return password_hash($pwd, PASSWORD_DEFAULT); } /** * This method crypt given string (password, session_id, …). * * @param string $pwd string to be crypted * @return string crypted value */ public function cryptLegacy($pwd) { return crypt::hmac(DC_MASTER_KEY, $pwd, DC_CRYPT_ALGO); } /** * This method only check current user password. * * @param string $pwd User password * @return boolean */ public function checkPassword($pwd) { if (!empty($this->user_info['user_pwd'])) { return password_verify($pwd, $this->user_info['user_pwd']); } return false; } /** * This method checks if user session cookie exists * * @return boolean */ public function sessionExists() { return isset($_COOKIE[DC_SESSION_NAME]); } /** * This method checks user session validity. * * @return boolean */ public function checkSession($uid = null) { $this->core->session->start(); # If session does not exist, logout. if (!isset($_SESSION['sess_user_id'])) { $this->core->session->destroy(); return false; } # Check here for user and IP address $this->checkUser($_SESSION['sess_user_id']); $uid = $uid ?: http::browserUID(DC_MASTER_KEY); $user_can_log = $this->userID() !== null && $uid == $_SESSION['sess_browser_uid']; if (!$user_can_log) { $this->core->session->destroy(); return false; } return true; } /** * Checks if user must change his password in order to login. * * @return boolean */ public function mustChangePassword() { return $this->user_change_pwd; } /** * Checks if user is super admin * * @return boolean */ public function isSuperAdmin() { return $this->user_admin; } /** * Checks if user has permissions given in $permissions for blog * $blog_id. $permissions is a coma separated list of * permissions. * * @param string $permissions Permissions list * @param string $blog_id Blog ID * @return boolean */ public function check($permissions, $blog_id) { if ($this->user_admin) { return true; } $p = array_map('trim', explode(',', $permissions)); $b = $this->getPermissions($blog_id); if ($b != false) { if (isset($b['admin'])) { return true; } foreach ($p as $v) { if (isset($b[$v])) { return true; } } } return false; } /** * Returns true if user is allowed to change its password. * * @return boolean */ public function allowPassChange() { return $this->allow_pass_change; } //@} /// @name User code handlers //@{ public function getUserCode() { $code = pack('a32', $this->userID()) . pack('H*', $this->crypt($this->getInfo('user_pwd'))); return bin2hex($code); } public function checkUserCode($code) { $code = @pack('H*', $code); $user_id = trim(@pack('a32', substr($code, 0, 32))); $pwd = @unpack('H*hex', substr($code, 32)); if ($user_id === false || $pwd === false) { return false; } $pwd = $pwd['hex']; $strReq = 'SELECT user_id, user_pwd ' . 'FROM ' . $this->user_table . ' ' . "WHERE user_id = '" . $this->con->escape($user_id) . "' "; $rs = $this->con->select($strReq); if ($rs->isEmpty()) { return false; } if ($this->crypt($rs->user_pwd) != $pwd) { return false; } return $rs->user_id; } //@} /// @name Sudo //@{ /** * Calls $f function with super admin rights. * Returns the function result. * * @param callback $f Callback function * @return mixed */ public function sudo($f) { if (!is_callable($f)) { throw new Exception($f . ' function doest not exist'); } $args = func_get_args(); array_shift($args); if ($this->user_admin) { $res = call_user_func_array($f, $args); } else { $this->user_admin = true; try { $res = call_user_func_array($f, $args); $this->user_admin = false; } catch (Exception $e) { $this->user_admin = false; throw $e; } } return $res; } //@} /// @name User information and options //@{ /** * Returns user permissions for a blog as an array which looks like: * * - [blog_id] * - [permission] => true * - ... * * @param string $blog_id Blog ID * @return array */ public function getPermissions($blog_id) { if (isset($this->blogs[$blog_id])) { return $this->blogs[$blog_id]; } if ($this->user_admin) { $strReq = 'SELECT blog_id ' . 'from ' . $this->blog_table . ' ' . "WHERE blog_id = '" . $this->con->escape($blog_id) . "' "; $rs = $this->con->select($strReq); $this->blogs[$blog_id] = $rs->isEmpty() ? false : array('admin' => true); return $this->blogs[$blog_id]; } $strReq = 'SELECT permissions ' . 'FROM ' . $this->perm_table . ' ' . "WHERE user_id = '" . $this->con->escape($this->user_id) . "' " . "AND blog_id = '" . $this->con->escape($blog_id) . "' " . "AND (permissions LIKE '%|usage|%' OR permissions LIKE '%|admin|%' OR permissions LIKE '%|contentadmin|%') "; $rs = $this->con->select($strReq); $this->blogs[$blog_id] = $rs->isEmpty() ? false : $this->parsePermissions($rs->permissions); return $this->blogs[$blog_id]; } public function getBlogCount() { if ($this->blog_count === null) { $this->blog_count = $this->core->getBlogs(array(), true)->f(0); } return $this->blog_count; } public function findUserBlog($blog_id = null) { if ($blog_id && $this->getPermissions($blog_id) !== false) { return $blog_id; } else { if ($this->user_admin) { $strReq = 'SELECT blog_id ' . 'FROM ' . $this->blog_table . ' ' . 'ORDER BY blog_id ASC ' . $this->con->limit(1); } else { $strReq = 'SELECT P.blog_id ' . 'FROM ' . $this->perm_table . ' P, ' . $this->blog_table . ' B ' . "WHERE user_id = '" . $this->con->escape($this->user_id) . "' " . "AND P.blog_id = B.blog_id " . "AND (permissions LIKE '%|usage|%' OR permissions LIKE '%|admin|%' OR permissions LIKE '%|contentadmin|%') " . "AND blog_status >= 0 " . 'ORDER BY P.blog_id ASC ' . $this->con->limit(1); } $rs = $this->con->select($strReq); if (!$rs->isEmpty()) { return $rs->blog_id; } } return false; } /** * Returns current user ID * * @return string */ public function userID() { return $this->user_id; } /** * Returns information about a user . * * @param string $n Information name * @return string */ public function getInfo($n) { if (isset($this->user_info[$n])) { return $this->user_info[$n]; } return; } /** * Returns a specific user option * * @param string $n Option name * @return string */ public function getOption($n) { if (isset($this->user_options[$n])) { return $this->user_options[$n]; } return; } /** * Returns all user options in an associative array. * * @return array */ public function getOptions() { return $this->user_options; } //@} /// @name Permissions //@{ /** * Returns an array with permissions parsed from the string $level * * @param string $level Permissions string * @return array */ public function parsePermissions($level) { $level = preg_replace('/^\|/', '', $level); $level = preg_replace('/\|$/', '', $level); $res = array(); foreach (explode('|', $level) as $v) { $res[$v] = true; } return $res; } /** * Returns perm_types property content. * * @return array */ public function getPermissionsTypes() { return $this->perm_types; } /** * Adds a new permission type. * * @param string $name Permission name * @param string $title Permission title */ public function setPermissionType($name, $title) { $this->perm_types[$name] = $title; } //@} /// @name Password recovery //@{ /** * Add a recover key to a specific user identified by its email and * password. * * @param string $user_id User ID * @param string $user_email User Email * @return string */ public function setRecoverKey($user_id, $user_email) { $strReq = 'SELECT user_id ' . 'FROM ' . $this->user_table . ' ' . "WHERE user_id = '" . $this->con->escape($user_id) . "' " . "AND user_email = '" . $this->con->escape($user_email) . "' "; $rs = $this->con->select($strReq); if ($rs->isEmpty()) { throw new Exception(__('That user does not exist in the database.')); } $key = md5(uniqid('', true)); $cur = $this->con->openCursor($this->user_table); $cur->user_recover_key = $key; $cur->update("WHERE user_id = '" . $this->con->escape($user_id) . "'"); return $key; } /** * Creates a new user password using recovery key. Returns an array: * * - user_email * - user_id * - new_pass * * @param string $recover_key Recovery key * @return array */ public function recoverUserPassword($recover_key) { $strReq = 'SELECT user_id, user_email ' . 'FROM ' . $this->user_table . ' ' . "WHERE user_recover_key = '" . $this->con->escape($recover_key) . "' "; $rs = $this->con->select($strReq); if ($rs->isEmpty()) { throw new Exception(__('That key does not exist in the database.')); } $new_pass = crypt::createPassword(); $cur = $this->con->openCursor($this->user_table); $cur->user_pwd = $this->crypt($new_pass); $cur->user_recover_key = null; $cur->user_change_pwd = 1; // User will have to change this temporary password at next login $cur->update("WHERE user_recover_key = '" . $this->con->escape($recover_key) . "'"); return array('user_email' => $rs->user_email, 'user_id' => $rs->user_id, 'new_pass' => $new_pass); } //@} /** @name User management callbacks This 3 functions only matter if you extend this class and use DC_AUTH_CLASS constant. These are called after core user management functions. Could be useful if you need to add/update/remove stuff in your LDAP directory or other third party authentication database. */ //@{ /** * Called after core->addUser * @see dcCore::addUser * @param cursor $cur User cursor */ public function afterAddUser($cur) {} /** * Called after core->updUser * @see dcCore::updUser * @param string $id User ID * @param cursor $cur User cursor */ public function afterUpdUser($id, $cur) {} /** * Called after core->delUser * @see dcCore::delUser * @param string $id User ID */ public function afterDelUser($id) {} //@} }