Documentation is available at user-defs.php
- <?php
- /* ******************************************************************** */
- /* CATALYST PHP Source Code */
- /* -------------------------------------------------------------------- */
- /* This program is free software; you can redistribute it and/or modify */
- /* it under the terms of the GNU General Public License as published by */
- /* the Free Software Foundation; either version 2 of the License, or */
- /* (at your option) any later version. */
- /* */
- /* This program is distributed in the hope that it will be useful, */
- /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
- /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
- /* GNU General Public License for more details. */
- /* */
- /* You should have received a copy of the GNU General Public License */
- /* along with this program; if not, write to: */
- /* The Free Software Foundation, Inc., 59 Temple Place, Suite 330, */
- /* Boston, MA 02111-1307 USA */
- /* -------------------------------------------------------------------- */
- /* */
- /* Filename: user-defs.php */
- /* Author: Paul Waite */
- /* Description: Definitions for managing USERS */
- /* */
- /* ******************************************************************** */
- /** @package core */ * This is the default. */
- define("LOCAL_AUTH", 0);
- /** User data is maintained on a remote database */
- ("REMOTE_AUTH_REMOTEDB", 1);
- /** User data maintained on an LDAP server */
- ("REMOTE_AUTH_LDAP", 2);
- /** Used to indicate items do not have a remote mapping */
- ("NOT_MAPPED", "");
- //-----------------------------------------------------------------------
- /**
- * The user class
- * This class managed users. It pre-supposes a particular database
- * structure based on three tables: uuser, ugroup, and uuser_group.
- * Please see the example schemas for Phplib for further details.
- * @package core
- */
- class user {
- /** Login user id, string */
- var $userid = "";
- /** True if user record is valid */
- var $valid = false;
- /** Optional authorisation hash code */
- var $auth_code = "";
- /** Formatted full display name of the person */
- var $name = "";
- /** Honorific prefix Eg. 'Mr.', 'Ms.', 'Mrs.' etc. */
- var $honorific_prefix = "";
- /** First name of the person */
- var $first_name = "";
- /** Middle names or initials of the person */
- var $mid_names = "";
- /** Last name of the person */
- var $last_name = "";
- /** Text password (encrypted or plain) */
- var $password = "";
- /** User e-mail address */
- var $email = "";
- /** User type: arbitrary textual type */
- var $user_type = "";
- /** True of user is active/enabled */
- var $enabled = false;
- /** Total logins so far */
- var $total_logins = 0;
- /** Limit of logins allowed (0=unlimited) */
- var $limit_logins = 0;
- /** True if user has a group membership */
- var $hasgroups = false;
- /** Array of group membership names (strings) */
- var $group_names = array();
- /** Group membership details in full, as associative array */
- var $group_info;
- /** Group membership count */
- var $user_groups_cnt = 0;
- /** Complete user record as an associative array */
- var $user_record;
- /** List of IP addresses this user will be auto-logged-in from. */
- var $IP;
- /** Flag, true if user has auto-login IP addresses */
- var $hasIPlist = false;
- /** Flag, true if user password never expires */
- var $passwd_forever = false;
- /** Date-time that the password will expire at (Unix timestamp)*/
- var $passwd_expiry_ts = 0;
- /** Array of previously used passwords */
- var $passwd_history = array();
- /** Number of consequetive password failures we have had */
- var $passwd_failures = 0;
- /** Flag, true if this user account is locked */
- var $locked;
- /** Security profile: how passwords are encrypted:
- * 'none', 'md5', 'md5salted', 'custom' */
- var $passwd_encryption = "md5";
- /** Security profile: no. of days a password lasts */
- var $passwd_expiry_days = 90;
- /** Security profile: no. of consequetive password failures allowed */
- var $passwd_max_attempts = 5;
- /** Security profile: delay in millisec after a password failure */
- var $passwd_delay_ms = 0;
- /** Security profile: min characters in a new password */
- var $passwd_min_chars = 6;
- /** Security profile: char uniqueness level - none, low, medium, or high */
- var $passwd_char_uniqueness = "medium";
- /** Security profile: if true, passwords must be mixture of alpha & numeric */
- var $passwd_alphanum_mixed = false;
- /** Security profile: if true, passwords must not match built-in stopword list */
- var $passwd_apply_stopwords = false;
- /** Security profile: password history cycle - number of saved passwords */
- var $passwd_history_cycle = 0;
- /** User authentication source */
- var $remote_auth_source = LOCAL_AUTH;
- /** User authentication method */
- var $authentication_method = "md5";
- /** User authentication remote database name */
- var $remote_auth_dbname = NOT_MAPPED;
- /** User authentication remote user table */
- var $remote_auth_tablename = NOT_MAPPED;
- /** User authentication remote table field mapping */
- var $remote_auth_fields = array();
- // .....................................................................
- /**
- * Constructor
- * Create a new user object.
- * @param string $userid User ID of the user
- */
- function user($userid="") {
- global $RESPONSE;
- $this->userid = $userid;
- // A special case provided for making a brand new
- // user object for a new user record..
- if ($userid == "#new#") {
- // A new user profile..
- $this->valid = true;
- $this->name = "--enter user name--";
- $this->user_type = "user";
- $this->group_info[2] = "User"; // User
- $this->group_names[] = "User"; // User
- $this->user_groups_cnt = 1;
- $this->hasgroups = true;
- $this->userid = "<enter user id>";
- }
- // A normal user..
- else {
- // If id supplied, get user details..
- if ($userid != "") {
- $this->get_user_by_id($userid);
- }
- }
- return $this->valid;
- } // user
- // .....................................................................
- /**
- * Set the user authentication method. This determines how we authenticate
- * the user. Normally we just authenticate via the local database, but this
- * method allows that to be varied for remotely maintained account details.
- * @param integer $auth_method Code for auth moethod 0=local, 1=remote db
- * @param string $auth_dbname Name of the remote database
- * @param string $auth_tablename Name of the remote database table
- * @param array $auth_mappings Array of field mappings for account info
- */
- function set_remote_authentication(
- $auth_source = 0,
- $auth_method = "md5",
- $auth_dbname = "",
- $auth_tablename = "",
- $auth_mappings = false
- ) {
- $this->remote_auth_source = $auth_source;
- $this->authentication_method = $auth_method;
- $this->remote_auth_dbname = $auth_dbname;
- $this->remote_auth_tablename = $auth_tablename;
- $this->remote_auth_fields = array();
- if (is_array($auth_mappings)) {
- $this->remote_auth_fields = $auth_mappings;
- foreach ($auth_mappings as $local_fieldname => $remote_fieldname) {
- if ($local_fieldname != "") {
- $this->remote_auth_fields[$local_fieldname] = $remote_fieldname;
- }
- }
- }
- } // set_remote_authentication
- // .....................................................................
- /**
- * Set the user security profile. This is a bunch of parameters which will
- * are applied to ALL users, including this one, when passwords are being
- * set, created or otherwise checked.
- * @param string $encryption Password encryption: 'none', 'md5', 'md5salted', 'custom'
- * @param integer $expiry_days No. of days passwords last before expiring
- * @param integer $max_attempts Max. no. of consequetive failed logins
- * @param boolean $history_cycle No. saved passwords before cycling the list
- * @param integer $delay_ms Delay in mS, for a failed login
- * @param integer $min_chars Minimum characters in a new password
- * @param boolean $char_uniqueness Char uniqueness level ('low', 'medium', 'high')
- * @param boolean $alphanum_mixed Whether a mix of alpha and numerics are required
- * @param boolean $apply_stopwords Whether to apply stopword list to passwords
- */
- function set_security_profile(
- $encryption = "md5",
- $expiry_days = 0,
- $max_attempts = 0,
- $history_cycle = 0,
- $delay_ms = 0,
- $min_chars = 0,
- $char_uniqueness = "low",
- $alphanum_mixed = false,
- $apply_stopwords = false
- ) {
- $this->passwd_encryption = $encryption;
- if ($encryption == "custom") {
- if (!function_exists("custom_generate_password")
- || !function_exists("custom_authenticate_password")) {
- $this->passwd_encryption = "md5";
- debugbr("Warning: set_security_profile: custom password "
- . "handlers are undefined - falling back to md5.",
- DBG_AUTH
- );
- }
- }
- $this->passwd_expiry_days = $expiry_days;
- $this->passwd_max_attempts = $max_attempts;
- $this->passwd_history_cycle = $history_cycle;
- $this->passwd_delay_ms = $delay_ms;
- $this->passwd_min_chars = $min_chars;
- $this->passwd_char_uniqueness = $char_uniqueness;
- $this->passwd_alphanum_mixed = $alphanum_mixed;
- $this->passwd_apply_stopwords = $apply_stopwords;
- } // set_security_profile
- // .....................................................................
- /**
- * Set the user login password. Store it according to the encryption
- * mode. We assume a plain text password is being supplied.
- * NB: Axyl-encrypted passwords always have an 'axenc_' prefix.
- * @param string $password Plain text password to set for this user
- * $return string The password which is going to be stored
- */
- function set_password($plaintext_password) {
- // Generate the password string we will be storing..
- $new_password = $this->generate_password($plaintext_password);
- debugbr("set_password: password changed", DBG_AUTH);
- // Push old password, if we have history..
- $this->push_password_history();
- // Reset the expiry too..
- $this->set_password_expiry();
- debugbr("set_password: new expiry set to: "
- . timestamp_to_displaydate(NICE_FULLDATETIME, $this->passwd_expiry_ts),
- DBG_AUTH
- );
- // Assign new password..
- $this->password = $new_password;
- $this->user_record["password"] = $this->password;
- return $this->password;
- } // set_password
- // .....................................................................
- /**
- * Authenticate a password according to the appropriate encryption regime.
- * The encryption method used depends on whether the user is a normal (local)
- * Axyl user, or one which is being maintained on a remote system.
- * @param string $submitted_passwd Password submitted for authentication.
- * @return boolean True if the password was authenticated.
- */
- function authenticate_password($submitted_passwd) {
- // Pessimism is good..
- $authenticated = false;
- // Determine which encryption method to use: local or remote..
- $passwd_encryption = $this->passwd_encryption;
- if ($this->user_type == "remote") {
- $passwd_encryption = $this->authentication_method;
- }
- // First, always try to match plain text, case-sensitive..
- $authmsg = "";
- if ($this->password == $submitted_passwd) {
- $authenticated = true;
- $authtype = "plaintext";
- if ($this->passwd_encryption != "none") {
- debugbr("authenticate_password: plaintext password should be "
- . "encrypted.",
- DBG_AUTH
- );
- }
- }
- else {
- // Encrypted passwords..
- switch ($passwd_encryption) {
- // Standard Axyl md5 encoded password..
- case "md5":
- if ($this->password == "axenc_" . md5($submitted_passwd)) {
- $authenticated = true;
- $authtype = "md5-axyl";
- }
- break;
- // Salted md5 formatted passwords (kudos to AWM)..
- case "md5salted":
- // Check for a '**whatever' administrator-brute-forced password..
- if (ereg("^\*\*.+$", $this->password)) {
- if ("**$submitted_passwd" == $this->password) {
- $authenticated = true;
- $authtype = "**admin";
- }
- }
- if (!$authenticated) {
- $matches = array();
- if (ereg("^\*(.+)\*.+$", $this->password, $matches)) {
- // A salted md5 formatted as "*<salt>*<salted_md5>"
- $salt = $matches[1];
- $password = sprintf("*%s*%s", $salt, md5($salt . $submitted_passwd));
- if ($this->password == $password) {
- $authenticated = true;
- $authtype = "md5-salted";
- }
- }
- }
- break;
- // Custom authentication algorithm defined in application.php..
- case "custom":
- if (custom_authenticate_password($submitted_passwd, $this->password)) {
- $authenticated = true;
- $authtype = "custom";
- }
- break;
- // Last chance saloon - case-insensitive plaintext match..
- default:
- if (strcasecmp($this->password, $submitted_passwd) == 0 ) {
- $authenticated = true;
- $authtype = "plain";
- }
- } // switch
- }
- // For the record..
- if ($authenticated) {
- debugbr("authenticate_password: authenticated ok ($authtype)", DBG_AUTH);
- }
- else {
- debugbr("authenticate_password: failed.", DBG_AUTH);
- }
- return $authenticated;
- } // authenticate_password
- // .....................................................................
- /**
- * Generate a new password. Although we take note of whether the user is
- * local or remote, in general we don't expect to be generating passwords
- * for remotely maintained users.
- * @param string $plaintext_password The plaintext password we will use
- * @param string $salt Optional salt for MD5 salted passwords
- */
- function generate_password($plaintext_password, $salt="") {
- // Which encryption method to use: local or remote..
- $passwd_encryption = $this->passwd_encryption;
- if ($this->user_type == "remote") {
- $passwd_encryption = $this->authentication_method;
- }
- switch ($passwd_encryption) {
- // Standard Axyl prefixed md5..
- case "md5":
- $new_password = "axenc_" . md5($plaintext_password);
- break;
- // A salted md5 string..
- case "md5salted":
- if ($salt == "") {
- $salt = substr(md5(rand(100000,999999)), 2, 8);
- }
- $new_password = sprintf("*%s*%s", $salt, md5($salt . $plaintext_password));
- break;
- // Custom password generation algorithm..
- case "custom":
- $new_password = custom_generate_password($plaintext_password, $salt);
- break;
- // Plain-text default..
- default:
- $new_password = $plaintext_password;
- } // switch
- // Return the resulting password..
- return $new_password;
- } // generate_password
- // .....................................................................
- /**
- * Save the password data as stored in this object, to the user record.
- * $return boolean True if the data was saved ok.
- */
- function save_password_data() {
- $result = true;
- if ($this->user_type != "remote") {
- $uup = new dbupdate("ax_user");
- $uup->set("password", $this->password);
- $uup->set("passwd_expiry", timestamp_to_datetime($this->passwd_expiry_ts));
- $uup->set("passwd_history", implode("^_^", $this->passwd_history));
- $uup->where("user_id='" . escape_string($this->userid) . "'");
- $result = $uup->execute();
- }
- return $result;
- } // save_password_data
- // .....................................................................
- /**
- * Check whether the password for this user has expired. Returns true
- * if it has, else false.
- * $return boolean True if this user has an expired password.
- */
- function password_expired() {
- $expired = false;
- if ($this->user_type != "remote") {
- if (!$this->passwd_forever) {
- if (time() >= $this->passwd_expiry_ts) {
- debugbr("password_expired: ["
- . timestamp_to_displaydate(NICE_FULLDATETIME, $this->passwd_expiry_ts)
- . "]",
- DBG_AUTH
- );
- $expired = true;
- }
- }
- }
- return $expired;
- } // password_expired
- // .....................................................................
- /**
- * Set the password expiry timestamp afresh. We use the settings for
- * how long passwords should last, and add this to the time now to
- * get the expiry datetime.
- */
- function set_password_expiry() {
- $this->passwd_expiry_ts = time() + ($this->passwd_expiry_days * SECS_1_DAY);
- } // set_password_expiry
- // .....................................................................
- /**
- * Push the current password on the history stack. Trim the history
- * to the number we are supposed to retain in the cycle. This method
- * only does anything if 'passwd_cycle_history' is non-zero. It also
- * checks and makes sure that the password isn't already in the
- * history array, and if it is, does nothing.
- */
- function push_password_history() {
- if ($this->passwd_history_cycle > 0) {
- $found = false;
- foreach ($this->passwd_history as $used_password) {
- if ($this->password == $used_password) {
- $found = true;
- break;
- }
- }
- if (!$found) {
- array_push($this->passwd_history, $this->password);
- while (count($this->passwd_history) > $this->passwd_history_cycle) {
- array_shift($this->passwd_history);
- }
- }
- }
- } // push_password_history
- // .....................................................................
- /**
- * Validate password against all the rules for it. Returns true if the
- * password passed all the tests, else false. Also provides a resulting
- * error message which is either a nullstring "", or an explanation of
- * why the validation failed.
- * @param string $password Plain text password to validate
- * @param string An array of error message explaining failure.
- * @return boolean True if password validated ok, else false.
- */
- function valid_password($password, &$errmsgs) {
- // Initialise..
- $valid = true;
- $errmsgs = array();
- // Length of the password..
- $passwd_length = strlen($password);
- // Check password length..
- if ($passwd_length < $this->passwd_min_chars ) {
- $errmsgs[] = "Please provide a password longer than $this->passwd_min_chars characters.";
- $valid = false;
- }
- // Trivial case, must be ok after length check..
- if ($passwd_length == 0) {
- return true;
- }
- // Check character uniqueness..
- if ($this->passwd_char_uniqueness != "none") {
- $required_uniqueness = 0;
- switch ($this->passwd_char_uniqueness) {
- case "low": $required_uniqueness = 0.20; break;
- case "medium": $required_uniqueness = 0.60; break;
- case "high": $required_uniqueness = 0.80; break;
- } // switch
- $unique = strlen(count_chars($password, 3));
- $uniqueness = $unique / $passwd_length;
- if ($uniqueness < $required_uniqueness) {
- $errmsgs[] = "Please provide more unique characters - too many repeated.";
- $valid = false;
- }
- }
- // Check password history..
- if ($this->passwd_history_cycle > 0 && count($this->passwd_history) > 0) {
- foreach ($this->passwd_history as $used_password) {
- $test_password = $this->generate_password($password);
- if (ereg("^\*(.+)\*.+$", $used_password, $matched)) {
- $salt = $matched[1];
- if ($salt != "") {
- $test_password = sprintf("*%s*%s", $salt, md5($salt . $password));
- }
- }
- if ($used_password == $test_password) {
- $errmsgs[] = "That password has been used in the past. Please invent another.";
- $valid = false;
- break;
- }
- }
- }
- // Check mixture of alpha & numerics..
- if ($this->passwd_alphanum_mixed) {
- if (!preg_match("/[A-z.;:!@#%^&-_+]+[0-9]+[A-z.;:!@#%^&-_+]+/", $password)) {
- $errmsgs[] = "Please provide a mixture of numbers and letters, but starting "
- . "and ending with letters.";
- $valid = false;
- }
- }
- // Check for common stop-words..
- if ($this->passwd_apply_stopwords) {
- // Add localised custom words first..
- $badwords = "password passw0rd passwd pass secret safe qwerty asdf earth mars venus pluto sexy";
- $badwords .= "linux unix microsoft wizard guru gandalf";
- $badwords .= " " . $this->full_name;
- $badwords .= " " . $this->first_name;
- $badwords .= " " . $this->mid_names;
- $badwords .= " " . $this->last_name;
- $badwords .= " " . "axyl";
- $badwords .= " " . APP_NAME;
- $badwords .= " " . APP_PREFIX;
- $badwords .= " " . $this->http_host;
- // Now add the common passwords dictionary..
- $AXYL_CONF = "/etc/axyl/axyl.conf";
- if (file_exists($AXYL_CONF)) {
- $result = exec("grep \"AXYL_HOME=\" $AXYL_CONF");
- if ($result != "") {
- $bits = explode("=", $result);
- if (is_dir($bits[1])) {
- $AXYL_HOME = $bits[1];
- $PASSWD_DICT = "$AXYL_HOME/misc/common_passwords.txt";
- if (file_exists($PASSWD_DICT)) {
- $dict = new inputfile($PASSWD_DICT);
- if ($dict->opened) {
- $badwords .= " " . $dict->readall();
- $dict->closefile();
- }
- }
- }
- }
- }
- // Now test the password..
- $passwords = explode(" ", $password);
- $foundbad = array();
- foreach ($passwords as $word) {
- if (stristr($badwords, $word)) {
- $foundbad[$word] = true;
- }
- }
- if (count($foundbad) > 0) {
- $errmsgs[] = "The password is too weak - insecure words found: " . implode(", ", array_keys($foundbad));
- $valid = false;
- }
- }
- // Return result..
- return $valid;
- } // valid_password
- // .....................................................................
- /**
- * Authenticate a user
- * Tries all types of authentication we know about using the parameters
- * passed to it.
- * @param string $authid Unique user ID, authorization code or IP
- * @param string $password Password for the user
- * @return integer Login type code
- */
- function authenticate($authid, $password="") {
- $login_type = authenticate_userid($authid, $password);
- if ($login_type == LOGIN_UNKNOWN) {
- $login_type = authenticate_ip($authid);
- if ($login_type == LOGIN_UNKNOWN) {
- $login_type = authenticate_authid($authid);
- }
- }
- if ($login_type == LOGIN_UNKNOWN) {
- $this->passwd_failures += 1;
- if ($this->passwd_failures > $this->passwd_max_attempts) {
- $this->locked = true;
- }
- }
- return $login_type;
- } // authenticate
- // .....................................................................
- /**
- * Authenticate a user by userid/password.
- * @param string $userid Unique user ID of the user
- * @param string $submitted_password Password for the user
- * @return integer Login type code
- */
- function authenticate_userid($userid, $submitted_password="") {
- $login_type = LOGIN_UNKNOWN;
- // Guest authentication..
- if (stristr($userid, "guest")) {
- if ($this->user("guest") && $this->enabled) {
- $login_type = LOGIN_BY_GUEST;
- }
- }
- // Authentication by userid and password..
- elseif ($this->get_user_by_id($userid)) {
- if ($this->enabled && !$this->locked) {
- if ($this->authenticate_password($submitted_password)) {
- $login_type = LOGIN_BY_PASSWD;
- }
- }
- }
- // Flag and return result..
- if ($login_type != LOGIN_UNKNOWN) {
- // If we care about failures, zeroize previous misdemeanours..
- if ($this->passwd_max_attempts > 0 && $this->passwd_failures > 0) {
- debugbr("authenticate_userid: zeroing password failures "
- . "(was $this->passwd_failures)",
- DBG_AUTH
- );
- $this->passwd_failures = 0;
- $lkup = new dbupdate("ax_user");
- $lkup->set("passwd_failures", $this->passwd_failures);
- $lkup->where("user_id='" . escape_string($this->userid) . "'");
- $lkup->execute();
- }
- }
- else {
- // If we care about failures, lock out suspicious login activity..
- if (!$this->locked && $this->passwd_max_attempts > 0 && $this->userid != "") {
- $this->passwd_failures += 1;
- if ($this->passwd_failures >= $this->passwd_max_attempts) {
- $this->locked = true;
- debugbr("authenticate_userid: password failures exceed limit "
- . "($this->passwd_max_attempts)",
- DBG_AUTH
- );
- }
- $lkup = new dbupdate("ax_user");
- $lkup->set("locked", $this->locked);
- $lkup->set("passwd_failures", $this->passwd_failures);
- $lkup->where("user_id='" . escape_string($this->userid) . "'");
- $lkup->execute();
- }
- // Implement failed login delay tactic, if enabled..
- if ($this->passwd_delay_ms > 0) {
- usleep($this->passwd_delay_ms * 1000);
- }
- // Report on it..
- $fmsg = "authenticate_userid: user [$userid] failed authentication.";
- if ($this->locked) {
- $fmsg .= " (account locked)";
- }
- debugbr($fmsg, DBG_AUTH);
- $this->valid = false;
- }
- return $login_type;
- } // authenticate_userid
- // .....................................................................
- /**
- * Authenticate a user by IP address
- * @param string $ip IP address of remote host accessing this site
- * @return integer Login type code
- */
- function authenticate_ipaddress($ip) {
- $login_type = LOGIN_UNKNOWN;
- // Authentication by IP..
- if ($this->get_user_by_ip($ip)) {
- if ($this->enabled && !$this->locked) {
- $login_type = LOGIN_BY_IP;
- }
- }
- // Flag and return result..
- if ($login_type != LOGIN_UNKNOWN) debugbr("IP address '$ip' was authenticated", DBG_DEBUG);
- else {
- $this->valid = false;
- $fmsg = "authenticate_ipaddress: IP address '$ip' failed authentication.";
- if ($this->locked) {
- $fmsg .= " (account locked)";
- }
- debugbr($fmsg, DBG_DEBUG);
- }
- return $login_type;
- } // authenticate_ipaddress
- // .....................................................................
- /**
- * Authenticate a user by authorisation ID
- * @param string $authid Authorisation code/id of the user
- * @return integer Login type code
- */
- function authenticate_authid($authid) {
- $login_type = LOGIN_UNKNOWN;
- // Authentication by unique authorsation code..
- if ($this->get_user_by_auth_code($authid)) {
- if ($this->enabled) {
- $login_type = LOGIN_BY_AUTHCODE;
- }
- }
- // Flag and return result..
- if ($login_type != LOGIN_UNKNOWN) debugbr("authid '$authid' was authenticated", DBG_DEBUG);
- else {
- $this->valid = false;
- $fmsg = "authenticate_authid: user '$authid' failed authentication.";
- if ($this->locked) {
- $fmsg .= " (account locked)";
- }
- debugbr($fmsg, DBG_DEBUG);
- }
- return $login_type;
- } // authenticate_authid
- // .....................................................................
- /**
- * Get user by ID
- * Internal function to return the user record from id.
- * @param string $userid Unique user ID
- * @return bool True if the user was found with the given user ID
- */
- function get_user_by_id($userid) {
- global $RESPONSE;
- debug_trace($this);
- $this->valid = false;
- $this->user_record = array();
- // Guests are always local..
- if ($userid == "guest") {
- $this->authentication_method = LOCAL_AUTH;
- }
- debugbr("get_user_by_id: auth source is $this->remote_auth_source", DBG_DEBUG);
- switch ($this->remote_auth_source) {
- case REMOTE_AUTH_REMOTEDB:
- debugbr("get_user_by_id: getting remote user [$userid]", DBG_AUTH);
- if ($RESPONSE->select_database($this->remote_auth_dbname)) {
- $q = "SELECT * FROM $this->remote_auth_tablename";
- $q .= " WHERE " . $this->remote_auth_fields["user_id"] . "='" . escape_string($userid) . "'";
- $remqu = dbrecordset($q);
- // Revert to default database..
- $RESPONSE->select_database();
- if ($remqu->hasdata) {
- // Got remote user record, now check if we have our copy of it..
- $qu = dbrecordset("SELECT * FROM ax_user WHERE user_id='" . escape_string($userid) . "'");
- if (!$qu->hasdata) {
- // Create new remote user, locally..
- debugbr("get_user_by_id: creating local copy of new remote user '$userid'", DBG_AUTH);
- start_transaction();
- $axins = new dbinsert("ax_user");
- foreach ($RESPONSE->remote_auth_fields as $axyl_fieldname => $remote_fieldname) {
- if ($remote_fieldname != NOT_MAPPED && $remqu->field_exists($remote_fieldname)) {
- $axins->set($axyl_fieldname, $remqu->field($remote_fieldname));
- $this->user_record["$axyl_fieldname"] = $remqu->field($remote_fieldname);
- }
- else {
- $this->user_record["$axyl_fieldname"] = "";
- }
- }
- // Assign the correct user type..
- $axins->set("user_type", "remote");
- $axins->execute();
- // And create the basic group membership too..
- $axng = new dbinsert("ax_user_group");
- $axng->set("user_id", $userid);
- $axng->set("group_id", 2); // normal user
- $axng->execute();
- commit();
- }
- else {
- // Initialise to Axyl record retrieved..
- $this->user_record = $qu->current_row;
- // Check data, refresh if anything has changed..
- $axupd = new dbupdate("ax_user");
- $axupd->where("user_id='" . escape_string($userid) . "'");
- $user_refresh = false;
- foreach ($RESPONSE->remote_auth_fields as $axyl_fieldname => $remote_fieldname) {
- if ($remote_fieldname != NOT_MAPPED && $remqu->field_exists($remote_fieldname)) {
- $axval = $qu->field($axyl_fieldname);
- $remval = $remqu->field($remote_fieldname);
- if ($axval != $remval) {
- $user_refresh = true;
- $axupd->set($axyl_fieldname, $remval);
- $this->user_record["$axyl_fieldname"] = $remval;
- }
- }
- }
- // Refresh if required.
- if ($user_refresh) {
- debugbr("get_user_by_id: refreshing local copy of remote user [$userid]", DBG_AUTH);
- $axupd->execute();
- }
- }
- // Now process Axyl user as normal..
- $this->valid = true;
- $this->assign_vars();
- }
- else {
- debugbr("get_user_by_id: remote falling back to local [$userid]", DBG_AUTH);
- $qu = dbrecordset("SELECT * FROM ax_user WHERE user_id='" . escape_string($userid) . "'");
- if ($qu->hasdata) {
- if ($qu->field("user_type") == "remote") {
- // Remote user has been deleted, so delete locally too..
- $axdel = new dbdelete("ax_user");
- $axdel->where("user_id='" . escape_string($userid) . "'");
- $axdel->execute();
- debugbr("get_user_by_id: remote user [$userid] not found - culling local copy.", DBG_AUTH);
- }
- else {
- // This is the case of a purely local user..
- $this->authentication_method = LOCAL_AUTH;
- $this->user_record = $qu->current_row;
- $this->valid = true;
- $this->assign_vars();
- }
- }
- }
- }
- break;
- case LOCAL_AUTH:
- default:
- debugbr("get_user_by_id: getting local [$userid]", DBG_AUTH);
- $qu = dbrecordset("SELECT * FROM ax_user WHERE user_id='" . escape_string($userid) . "'");
- if ($qu->hasdata) {
- $this->user_record = $qu->current_row;
- $this->valid = true;
- $this->assign_vars();
- }
- break;
- } // switch
- debug_trace();
- return $this->valid;
- } // get_user_by_id
- // .....................................................................
- /**
- * Get user by Authorisation Code
- * Internal function to return the user record from auth_code. The
- * authorisation code is usually a string containing a complex key
- * generated by something like MD5 or better.
- * @param string $auth_code Authorisation code to match for this user
- * @return bool True if the user was found with the given authorisation code
- */
- function get_user_by_auth_code($auth_code) {
- debug_trace($this);
- $this->valid = false;
- debugbr("get_user_by_auth_code: getting '$auth_code'", DBG_DEBUG);
- $qu = dbrecordset("SELECT * FROM ax_user WHERE auth_code='$auth_code'");
- if ($qu->hasdata) {
- $this->user_record = $qu->current_row;
- $this->valid = true;
- $this->assign_vars();
- }
- debug_trace();
- return $this->valid;
- } // get_user_by_auth_code
- // .....................................................................
- /**
- * Get user by IP
- * Internal function to return the user record which has IP address(es)
- * which coincide with the client IP address being used for this access.
- * @param string $ip Allowed IP host or network to allow logins from
- * @return bool True if a user was found with matching IP address
- */
- function get_user_by_ip($ip) {
- global $RESPONSE;
- debug_trace($this);
- debugbr("get_user_by_ip: getting '$ip'", DBG_DEBUG);
- $this->valid = false;
- if ($ip != "") {
- if (is_ipaddress($ip)) {
- switch ($RESPONSE->datasource->dbtype()) {
- case "postgres":
- // PostgreSQL support special inet datatype..
- $qu = dbrecordset("SELECT * FROM ax_user_ip WHERE ip >>= inet '$ip'");
- break;
- default:
- // All others use string comparison..
- $qu = dbrecordset("SELECT * FROM ax_user_ip WHERE ip='$ip'");
- }
- if ($qu->hasdata) {
- $userid = $qu->field("user_id");
- $ipcount = $qu->rowcount;
- $qu = dbrecordset("SELECT * FROM ax_user WHERE user_id='". escape_string($userid) . "'");
- if ($qu->hasdata) {
- $this->user_record = $qu->current_row;
- $this->valid = true;
- $this->assign_vars();
- debugbr("IP auth success. User=$this->userid", DBG_DEBUG);
- }
- $qip = dbrecordset("SELECT * FROM ax_user_ip WHERE user_id='". escape_string($userid) . "'");
- if ($qip->hasdata) {
- if (isset($this->IP)) unset($this->IP);
- $this->hasIPlist = true;
- do {
- $this->IP[] = $qip->field("ip");
- } while ($qip->get_next());
- }
- // Warning for badly defined IP login data..
- if ($ipcount > 1) {
- debugbr("WARNING: IP-based login overlap: $ipcount matches for Remote IP='$ip'", DBG_DEBUG);
- }
- }
- else {
- debugbr("get_user_by_ip: failed to authenticate '$ip'", DBG_DEBUG);
- }
- }
- }
- debug_trace();
- return $this->valid;
- } // get_user_by_ip
- // ....................................................................
- /**
- * Get user Authorisation Code
- * Return this user's unique authorisation code; generate
- * one if it isn't there yet, from userid and current time.
- * @return string The authorisation code for the current user
- */
- function get_auth_code() {
- if ($this->valid) {
- if ($this->auth_code == "") {
- debug_trace($this);
- $seed = $this->userid . $this->name . microtime();
- $this->auth_code = md5($seed);
- $this->user_record["auth_code"] = $this->auth_code;
- dbcommand("UPDATE ax_user SET auth_code='$this->auth_code' WHERE user_id='" . escape_string($this->userid) . "'");
- debug_trace();
- }
- return $this->auth_code;
- }
- else {
- return false;
- }
- } // get_auth_code
- // ....................................................................
- /**
- * Get user groups info
- * For this user, populate the group data for this object. We
- * read the uuser_group and ugroup tables and populate the
- * two variables @see $user_groups and @see $group_info
- * @return string The groups list for the user, delimited by pipe ("|")
- */
- function get_groups() {
- // Initialise..
- $ugroups = "";
- if (isset($this->group_info)) unset($this->group_info);
- // User group data acquisition..
- $q = "SELECT *";
- $q .= " FROM ax_user_group ug, ax_group g";
- $q .= " WHERE ug.user_id='" . escape_string($this->userid) . "'";
- $q .= " AND g.group_id=ug.group_id";
- $group = dbrecordset($q);
- if ($group->hasdata) {
- $this->hasgroups = true;
- do {
- $groupid = $group->field("group_id");
- $groupname = $group->field("group_desc");
- $this->group_info[$groupid] = $groupname;
- if ($ugroups != "") $ugroups .= "|";
- $ugroups .= $groupname;
- } while ($group->get_next());
- }
- // Force guest users into Guest group..
- if (stristr($this->userid, "guest")) {
- $ugroups = "Guest";
- }
- // Assign the group list and count..
- $this->group_names = explode("|", $ugroups);
- $this->user_groups_cnt = count($this->group_names);
- // A by-product..
- return $ugroups;
- } // get_groups
- // ....................................................................
- /**
- * Assign user variables
- * Internal function to assign variables from new record..
- * @access private
- */
- function assign_vars() {
- global $RESPONSE;
- debug_trace($this);
- if ($this->valid) {
- $this->userid = unescape_string($this->user_record["user_id"]);
- $this->name = unescape_string($this->user_record["full_name"]);
- $this->honorific_prefix = unescape_string($this->user_record["honorific_prefix"]);
- $this->first_name = unescape_string($this->user_record["first_name"]);
- $this->mid_names = unescape_string($this->user_record["mid_names"]);
- $this->last_name = unescape_string($this->user_record["last_name"]);
- $this->email = $this->user_record["email"];
- $this->password = $this->user_record["password"];
- if ($this->passwd_encryption == "none") {
- $this->password = unescape_string($this->password);
- }
- $this->auth_code = $this->user_record["auth_code"];
- $this->user_type = $this->user_record["user_type"];
- $this->total_logins = $this->user_record["total_logins"];
- $this->limit_logins = $this->user_record["limit_logins"];
- $this->passwd_forever = $RESPONSE->datasource->bool_from_db_value($this->user_record["passwd_forever"]);
- $this->passwd_expiry_ts = datetime_to_timestamp($this->user_record["passwd_expiry"]);
- $this->last_login_ts = datetime_to_timestamp($this->user_record["last_login"]);
- if ($this->user_record["passwd_history"] != "") {
- $this->passwd_history = explode("^_^", $this->user_record["passwd_history"]);
- }
- $this->passwd_failures = $this->user_record["passwd_failures"];
- $this->locked = $RESPONSE->datasource->bool_from_db_value($this->user_record["locked"]);
- $this->enabled = $RESPONSE->datasource->bool_from_db_value($this->user_record["enabled"]);
- // Get groups info..
- $this->get_groups();
- }
- else {
- $this->name = "Error: User not found";
- $this->user_type = "user";
- }
- debug_trace();
- } // assign_vars
- // ....................................................................
- /**
- * Is user a member of a named group. The argument passed in must be a
- * single group name string (ie. not a numeric group id) which is defined
- * in the database.
- * Return true if the user is a member of the named group.
- * @param string $groupname Name of the group we are checking user membership of
- * @return bool True if the user is a member of the group, else false
- */
- function ismemberof_group($groupname) {
- $found = false;
- for ($i = 0; $i < $this->user_groups_cnt; $i++) {
- if (!strcasecmp(trim($this->group_names[$i]), trim($groupname))) {
- $found = true;
- break;
- }
- } // for
- return $found;
- } // ismemberof_group
- // ....................................................................
- /**
- * Is user a member of one group of many
- * Check user against a list of groups, return true if member of at
- * least one of them. The list in $groupnames can be either a comma-delimited
- * string of group names, OR an array of group names.
- * @param mixed $groupnames_list Comma-delimited list OR array of group names
- * @return bool True if user is member of at least one of the groups, else false
- */
- function ismemberof_group_in($groupnames_list) {
- if (is_string($groupnames_list)) {
- if (trim($groupnames_list) == "") {
- return true;
- }
- $groupnames = explode(",", $groupnames_list);
- }
- else {
- $groupnames = $groupnames_list;
- }
- $found = false;
- if (count($groupnames) == 0) {
- return true;
- }
- foreach ($groupnames as $groupname) {
- $found = $this->ismemberof_group($groupname);
- if ($found) break;
- }
- return $found;
- } // ismemberof_group_in
- // ....................................................................
- /**
- * Is user a member of a group with ID
- * Return true if the user is a member of the group with given ID.
- * @param string $groupid ID of the group we are checking user membership of
- * @return bool True if the user is a member of the group, else false
- */
- function ismemberof_group_with_id($groupid) {
- if (!isset($this->group_info)) {
- $this->get_groups();
- }
- return (isset($this->group_info[$groupid]));
- } // ismemberwith_groupid
- // ....................................................................
- /**
- * Return true if the current user is a valid one. This is false when the
- * user has not been authorised, or the user ID wasn't found etc. It is
- * an error condition for this to be false.
- * @return bool True if the current user object is valid
- */
- function isvalid() {
- return $this->valid;
- } // isvalid
- // ....................................................................
- /**
- * Get group IDs list
- * Return a string with the comma-delimited list of group ids which this
- * user belongs to in it. This is useful for using in an SQL statement like:
- * WHERE group_id IN (group_ids_list())
- * for example. Note we only access the database to populate $this->group_info
- * when we need to, not every session.
- * @param string $delim Delimiter character (defaults to comma)
- * @return string List of group ID's comma-delimited
- */
- function group_ids_list($delim=",") {
- if (!isset($this->group_info)) {
- $this->get_groups();
- }
- $gplist = array();
- if (isset($this->group_info)) {
- foreach ($this->group_info as $gid => $gdesc) {
- $gplist[] = $gid;
- }
- }
- return implode($delim, $gplist);
- } // group_ids_list
- // ....................................................................
- /**
- * Get group names list
- * Return a string with the comma-delimited list of group names which this
- * user belongs to in it. Eg. "Editor,Author,Admin"
- * @param string $delim Delimiter character (defaults to comma)
- * @return string List of group name's comma-delimited
- */
- function group_names_list($delim=",") {
- if (!isset($this->group_info)) {
- $this->get_groups();
- }
- $gplist = array();
- if (isset($this->group_info)) {
- foreach ($this->group_info as $gid => $gdesc) {
- $gplist[] = $gdesc;
- }
- }
- return implode($delim, $gplist);
- } // group_names_list
- // ....................................................................
- /**
- * Get friendly name
- * Make a 'friendly' name from a full one. Good for "Dear... ,"
- * @return string Friendly name for the current user
- */
- function friendlyName() {
- if ($this->valid) {
- $splitname = explode(" ", $this->name);
- $mate = trim($splitname[0]);
- if ($mate == "") $mate = $this->name;
- return $mate;
- }
- else return "Invalid User";
- } // friendlyName
- } // user class
- // ----------------------------------------------------------------------
- /**
- * The Authorised User class
- * This derived class just allows us a different way of defining
- * a new user, when we know their authorisation code.
- * @package core
- */
- class authorised_user extends user {
- // .....................................................................
- /**
- * Constructor
- * Create a new authorised user object.
- * @param string $auth_code Authorisation code of the user
- */
- function authorised_user($auth_code="") {
- $this->user();
- if ($auth_code != "") {
- $this->get_user_by_auth_code($auth_code);
- }
- } // authorised_user
- } // authorised_user class
- // ----------------------------------------------------------------------
- /**
- * The Permissions class. This generic class manages permissions for a
- * set of "agents" which are identified by a supplied "id". The permissions
- * are the standard Create, Read, Update, Delete or any combination by
- * ORing these values together.
- * @package core
- */
- // ACCESS MODES
- /** Permission to create items */
- ("PERM_CREATE", 0x01);
- /** Permission to read/view items */
- ("PERM_READ", 0x02);
- /** Permission to update/modify items */
- ("PERM_UPDATE", 0x04);
- /** Permission to delete items */
- ("PERM_DELETE", 0x08);
- /** All permitted */
- ("PERM_ALL", 0x0f);
- /** Nothing permitted */
- ("PERM_NONE", 0x00);
- // PERMISSION RETURN CODES
- /** Permission is given */
- ("PERM_ALLOWED", 1);
- /** Permission is refused */
- ("PERM_DISALLOWED", 2);
- /** Permission is undefined */
- ("PERM_UNDEFINED", 3);
- /** The default agent ID */
- ("DEFAULT_AGENT", "__perm_default_agent__");
- // ......................................................................
- /**
- * The permissions class. This class encpasulates a set of permissions
- * which can be managed and tested by the associated methods.
- * @package core
- */
- class permissions {
- /** Array of permisssions. This is an associative array with the
- key being the identifier of an agent which can be permitted or
- disallowed from accessing things, and the value being a
- permission code as defined above. */
- var $perms = array();
- // .....................................................................
- /**
- * Constructor
- * Create a new permissions object with an optional permissions set.
- * @param mixed $perms If provided, must be an array of permissions
- */
- function permissions($perms=false) {
- // Always include default perm..
- $this->permit(DEFAULT_AGENT, PERM_READ);
- if ( is_array($perms) ) {
- $this->perms = $perms;
- }
- } // permissions
- // .....................................................................
- /**
- * Assign the default permission. This is the permission which is applied
- * if the supplied agent is not recognised.
- * @param integer $perm The default permission to apply for unrecognised agents
- */
- function setdefault($perm) {
- $this->permit(DEFAULT_AGENT, $perm);
- } // setdefault
- // .....................................................................
- /**
- * Assign the given agent(s) the given access permission. The first paramter
- * is a (comma) delimited list of agent IDs to assign the permission to.
- * @param mixed $agentids Agents to assign the permission to (array or delimited string)
- * @param integer $perm The permission of combination of perms to assign
- * @param string $delim The delimiter string separating agent IDs (default comma)
- */
- function permit($agentids, $perm, $delim=",") {
- if (is_array($agentids)) $agents = $agentids;
- else $agents = explode($delim, $agentids);
- foreach ($agents as $agentid) {
- $this->perms[$agentid] = $perm;
- }
- } // permit
- // .....................................................................
- /**
- * Un-assign the given agent(s) the given access permission. The first paramter
- * is a (comma) delimited list of agent IDs to unassign the permission from.
- * @param mixed $agentids Agents to unassign the permission from (array or delimited string)
- * @param integer $perm The permission of combination of perms to unassign
- * @param string $delim The delimiter string separating agent IDs (default comma)
- */
- function unpermit($agentids, $perm, $delim=",") {
- if (is_array($agentids)) $agents = $agentids;
- else $agents = explode($delim, $agentids);
- foreach ($agents as $agentid) {
- if (isset($this->perms[$agentid])) {
- $unperm = $this->perms[$agentid] & $perm;
- $newperm = $this->perms[$agentid] ^ $unperm;
- $this->perms[$agentid] = $newperm;
- }
- }
- } // unpermit
- // .....................................................................
- /**
- * Low-level method for returning the permission for the given agent and
- * perm. We return one of three states: agent is allowed, agent is disallowed,
- * or agent permission status is undefined/unknown. The latter would occur
- * if the agent ID is unrecognised in this class (ie. not in the $perms array).
- * @param integer $agentid The unique agent ID to return the permission of
- * @param integer $perm The permission of combination of perms to assign
- * @return integer The permission status: allowed, disallowed or undefined
- */
- function permission($agentid, $perm) {
- if ( isset($this->perms[$agentid]) ) {
- return ($perm & $this->perms[$agentid] ? PERM_ALLOWED : PERM_DISALLOWED );
- }
- else {
- return PERM_UNDEFINED;
- }
- } // permission
- // .....................................................................
- /**
- * This is the main method for querying permission access rights for a given
- * agent. Returns a boolean value, true if the agent is permitted to access
- * in the given way, else false. If the agent ID is unrecognised, then the
- * method uses the 'default agent' permissions.
- * @param integer $agentid The agent to query the access permission of
- * @param integer $perm The access permission
- * @return boolean True if the agent is permitted access in given ways
- */
- function ispermitted($agentid, $perm) {
- $permission = $this->permission($agentid, $perm);
- if ($permission == PERM_UNDEFINED) {
- $permission = $this->permission(DEFAULT_AGENT, $perm);
- }
- return ($permission == PERM_ALLOWED);
- } // ispermitted
- // .....................................................................
- /**
- * This is a variant permitted query method, which takes a comma-delimited
- * list of agent IDs, and returns true if ANY one or more of these has the
- * required permissions. This facilitates passing of a group membership
- * list for a given user, for example.
- * @param mixed $agentids Agents to query the permission of (array or delimited string)
- * @param integer $perm The access permission
- * @param string $delim Delimiter character used (default is a comma)
- * @return boolean True if the agent is permitted access in given ways
- */
- function anypermitted($agentids, $perm, $delim=",") {
- $permitted = false;
- if (is_array($agentids)) $agents = $agentids;
- else $agents = explode($delim, $agentids);
- foreach ($agents as $agentid) {
- if ($this->ispermitted($agentid, $perm)) {
- $permitted = true;
- break;
- }
- }
- return $permitted;
- } // anypermitted
- // .....................................................................
- /**
- * Decode permission as a string of the form 'crud'
- * @param integer $perm The access permission to decode
- * @access private
- */
- function decode($perm) {
- $s = "";
- $s .= ($perm & PERM_CREATE ? "c" : "-");
- $s .= ($perm & PERM_READ ? "r" : "-");
- $s .= ($perm & PERM_UPDATE ? "u" : "-");
- $s .= ($perm & PERM_DELETE ? "d" : "-");
- return $s;
- } // decode
- // .....................................................................
- /** Dump these permissions as text. Mainly a debugging aid.
- * @access private
- */
- function dump() {
- $s = "";
- reset($this->perms);
- while (list($agentid, $perm) = each($this->perms)) {
- if ($agentid != DEFAULT_AGENT) {
- $s .= $agentid . " " . "(" . $this->decode($perm) . ") ";
- }
- }
- if ($s == "") $s = "no perms";
- return $s;
- } // dump
- } // permissions class
- // ----------------------------------------------------------------------
- ?>
Documentation generated by phpDocumentor 1.3.0RC3