. ** ** it_user.class - User management and authentication */ /* PUBLIC and guaranteed to stay in same order (but not value) forever */ define('IT_USER_STATUS_FAILED', 1); /* Wrong password given */ define('IT_USER_STATUS_UNKNOWN', 2); /* Not logged in, no UID */ define('IT_USER_STATUS_ANONYMOUS', 3); /* Not logged in, UID known */ define('IT_USER_STATUS_KNOWN', 4); /* Not logged in, UID,username known */ define('IT_USER_STATUS_SESSION', 5); /* Has a valid session */ /* PRIVATE */ define('_IT_USER_UID_COOKIE', 'UID'); define('_IT_USER_UID_COOKIE_LIFETIME', 0x7FFFFFFF); /* Forever :-) */ define('_IT_USER_STATUS_INVALID', 0); /* INTERNAL: Not yet evaluated */ define('_IT_USER_COOKIE_SAMESITE', 'Lax'); class it_user extends it_dbi { /* PRIVATE */ var $status; # Current status (IT_USER_STATUS_...) var $session; # Session object var $sessioninitialized = false; var $login_identifier_required = false; var $login_identifier; var $domain; var $lifetime; var $secret; var $urlauthenticationcode = 'uac'; # Name of UAC url parameter var $uid; var $username; /* * Used by login(), contains unvalidated user data to give overloading * functions a way of accessing it. */ var $workrecord; /** * Constructor * @param $p array(key => value) of configuration data */ function __construct($p = array()) { $this->p = $p + array( 'uid_field' => 'ID', 'table' => 'T_Users', 'username_field' => 'Username', 'password_field' => 'Password', 'uidcookiename' => _IT_USER_UID_COOKIE, 'sessioncookiename' => null ); # Default to uid being primary key, may change later parent::__construct(array('table' => $this->p['table'], 'keyfield' => $this->p['uid_field'])); $this->session = new it_session; $this->status = _IT_USER_STATUS_INVALID; } /** * Minimalistic post processing to fill uid and username fields after calling read() on it_user */ function read($id = null) { $result = parent::read($id); # If read succeeded, get UID. This is necessary because it's only set if a cookie is present (i.e. in web-context) if (isset($this->{$this->p['uid_field']})) $this->uid = $this->{$this->p['uid_field']}; # Get username from database field (shortcut) $this->username = $this->{$this->p['username_field']}; return $result; } function set_session_cookie_name($sessioncookiename) { $this->p['sessioncookiename'] = $sessioncookiename; } function set_uid_cookie_name($uidcookiename) { $this->p['uidcookiename'] = $uidcookiename; } function set_domain($domain) { $this->domain = $domain; } function set_session_lifetime($lifetime) { $this->lifetime = $lifetime; } function set_login_identifier($login_identifier) { $this->login_identifier_required = true; $this->login_identifier = $login_identifier; } function _init_session() { if (!$this->sessioninitialized) { # Using non-standard values for session? if ($this->p['sessioncookiename']) $this->session->set_cookiename($this->p['sessioncookiename']); if (isset($this->domain)) $this->session->set_domain($this->domain); if (isset($this->lifetime)) $this->session->set_lifetime($this->lifetime); if (isset($this->secret)) $this->session->set_secret($this->secret); $this->session->init(); $this->sessioninitialized = true; } } function get_status() { if ($this->status == _IT_USER_STATUS_INVALID) { $this->_init_session(); if ($this->session->is_valid()) { $this->status = IT_USER_STATUS_SESSION; $this->_set_uid($this->session->get_uid()); $this->read($this->uid); } else if (isset($_COOKIE[$this->p['uidcookiename']]) && ($this->uid = substr($_COOKIE[$this->p['uidcookiename']], 0, 32))) { @$this->read($this->uid); $this->status = $this->username ? IT_USER_STATUS_KNOWN : IT_USER_STATUS_ANONYMOUS; } else { $this->status = IT_USER_STATUS_UNKNOWN; $this->username = ''; if ($this->p['uid_field']) $this->_set_uid($this->create_uid()); } } return $this->status; } function get_username() { return $this->username; } function get_uid() { return $this->uid; } function _set_uid($uid) { $this->uid = $uid; if (!isset($_COOKIE[$this->p['uidcookiename']]) || ($_COOKIE[$this->p['uidcookiename']] != $uid)) { it::setcookie($this->p['uidcookiename'], $uid, [ 'expires' => _IT_USER_UID_COOKIE_LIFETIME, 'path' => "/", 'domain' => $this->domain, 'secure' => false, 'httponly' => true, 'samesite' => _IT_USER_COOKIE_SAMESITE ]); $_COOKIE[$this->p['uidcookiename']] = $uid; } } /* Return session status of this user: Is she logged in? */ function is_logged_in() { return $this->status == IT_USER_STATUS_SESSION; } /** * Try to log in user. Use get_status() to check result. * NOTE: Must not be called AFTER get_status() has been used. * @param $username User ID to login * @param $password Password to authenticate login * @param $ignorepassword True if you want to login anyway (e.g. 'su') * @return non-false if successful, false on error */ function login($username, $password, $ignorepassword = false, $withsession = true) { $result = false; $this->_init_session(); $this->workrecord = new it_dbi(array('server' => $this->_p['server'], 'table' => $this->p['table'], 'keyfield' => $this->p['username_field'])); if ($this->workrecord->read($username)) { if ($ignorepassword || $this->check_password($password, $this->workrecord->{$this->p['password_field']})) { $this->session->set_uid($this->workrecord->{$this->p['uid_field']}); if ($withsession) $result = $this->session->set_valid(true, $this->login_identifier_required, $this->login_identifier); else $result = $this->_set_uid($this->session->get_uid()); } } if ($result && ($this->session->get_uid() == $this->workrecord->{$this->p['uid_field']})) $this->username = $this->workrecord->{$this->p['username_field']}; $this->status = $result ? _IT_USER_STATUS_INVALID : IT_USER_STATUS_FAILED; return $result; } /* * Logout user. * NOTE: Must not be called AFTER get_status() has been used. */ function logout() { $this->_init_session(); $this->session->set_valid(false); } /* * Throw away all user information and restart from scratch... */ function purge() { $this->status = _IT_USER_STATUS_INVALID; $this->_set_uid($this->create_uid()); $this->username = ""; $this->session->purge(); } /** * Create user database record. * @param $tags Fields to set (uid and username are optional) * @see it_dbi::insert() */ function create($tags) { # Make sure UID is always set in database records if ($this->p['uid_field']) { if (!$this->uid) $this->_set_uid($this->create_uid()); $tags[$this->p['uid_field']] = $this->uid; } # Create dummy but unique username if none given if (!$tags[$this->p['username_field']] && !$this->{$this->p['username_field']}) $tags[$this->p['username_field']] = $this->uid; if ($result = $this->insert($tags)) $this->_set_uid($this->{$this->p['uid_field']}); return $result; } /* * Create unique identifier used for anonymously users, Override if you want * different type of UIDs. * Returns newly created uid */ function create_uid() { return bin2hex(random_bytes(16)); /* random garbage */ } /* * Create a login identifier and set session to login identifier 'secret' value * Returns a value to be put into the login