rendered paste body<?/************************************************** users.class.php -- * - Active user interface, cannot be substituted as generic comparison objects. * Built in controls to maintain, restrict, check & even alter access, automatically * syncronizing the database in the process* * Copyright(c) 2008 and beyond, Vanguard Technical and Media Services* - Author Bryan Steimel (04.02.08)*************************************************/// depends, db.class.php(db.abstract.php) - db.tables.phpdefine('USERS_TABLE_USER', 'ueas_user'); define('UACCOUNT_ATTEMPTS_LIMIT', 10);define('UACCOUNT_ATTEMPTS_LOCK', 60*60*6); // SECONDS! 6 hours.. failed attempts count reset after this same time period.. define('UACCOUNT_PWDMIN', 8);define('UACCOUNT_DEFAULT_FLAGS', 0);define('UACCOUNT_DEFAULT_FLAGS', 0);define('UACCOUNT_COOKIE_SSL', false); define('UACCOUNT_COOKIE_DOMAIN', 'test.europeanautosource.com'); define('UACCOUNT_COOKIE_PATH', '/admin/'); define('UACCOUNT_COOKIE_CAP', '__'); define('UACCOUNT_COOKIE_INACTIVE_EXPIRY', 0);define('UACCOUNT_AUTH_OFFSET', 2); define('UACCOUNT_AUTH_LEN', 30); // register users table; new dbt(USERS_TABLE_USER, array( username => array(DBT::TEXT, 32), hash => array(DBT::TEXT, 32), authcode => array(DBT::TEXT, 30), attempts_failure => array(DBT::INT, 2), attempts_last => array(DBT::INT, 11), lock_expiry => array(DBT::INT, 11), account_status => array(DBT::INT, 3, 1), logon_last => array(DBT::INT, 11), id => DBT::DENY )); // fuck it, I'm gonna throw exceptions as opposed to implementing an event handler.. class UExcept extends Exception { const INTERNAL = 2; const GENERIC = 0; const WARNING = 1; const LOCK = 100; public $messages = array(); public $codes = array(); public $code = self::INTERNAL; public function __construct($message = '', $code = self::INTERNAL) { $this->message = $message; $this->message = $this->message(); parent::__construct($this->format(), $code); // parent::__construct($message, $code); } public function message() { if(is_numeric($this->message) && array_key_exists($this->message, $GLOBALS['UE_MESSAGES'])) return $GLOBALS['UE_MESSAGES'][$this->message]; elseif(is_array($this->message)) { $this->codes = array_values($this->message); foreach($this->message as $val) array_push($this->messages, ($GLOBALS['UE_MESSAGES'][$val] ? $GLOBALS['UE_MESSAGES'][$val] : 'An unknown error has occured') ); return (array)$this->messages; } else return parent::getmessage(); } public function format($format = ' - %s<br />') { $txt = ''; if(is_array($t = $this->messages ? $this->messages : $this->getmessage())) foreach($t as $v) $txt .= sprintf($format, $v) . "\n"; else $txt = $t; return $txt; } public function register(array $list) { foreach($list as $k => $v) $GLOBALS['UE_MESSAGES'][$k] = $list[$k]; } public function uexit($message=null) { if($message) print '<b>' . $message . '<\b>' . "\n"; exit(2); } } $dbd = $GLOBALS['DBD']; //non-method functions, getting too cluttered down there is all.. class user_tools { const U_OK = true, ERR_EXISTS = 101, ERR_INVALID_USER = 111, ERR_INVALID_PWD = 121, ERR_INVALID_EMAIL = 131; static function retrieve($user, $id = false) { // $user/$id not interchangable! this will insure, its deliberate global $dbd; if(empty($user) && (!$id && is_null($user))) throw(new UExcept(__CLASS__ . '::retrieve() No user supplied')); $res = $dbd->query( $b = "SELECT username, hash, authcode, authsess, email, request_last, account_status, logon_last, lock_expiry, attempts_last, attempts_failure, id " . "FROM " . USERS_TABLE_USER . " WHERE " . ($id ? "id = '" . dbd::sanitize($user) . "'" : "lcase(" . (!strpos($user, '@') ? 'username' : 'email') . ") = '" . dbd::sanitize($user) . "'" ) ); return is_object($res) && $res->rows() ? $res : false; } static public function add($username, $email, $password, $verify = true) { global $dbd; $e = array(); if(($res = self::retrieve($username)) !== false) throw(new UExcept(self::ERR_EXISTS, UExcept::WARNING)); if(!preg_match('/^[^0-9][a-z0-9\.\-\_]+$/i', $username)) $e[] = self::ERR_INVALID_USER; /* do this second, as to go ahead and account for any possibility in the db */ if(strlen($password) < UACCOUNT_PWDMIN) $e[] = self::ERR_INVALID_PWD; if(!preg_match('/^(?:(?!@).*)@.*\.[a-z]{2,}$/', $email)) $e[] = self::ERR_INVALID_EMAIL; if(!empty($e)) throw(new UExcept($e, UExcept::WARNING)); $dbd->insert(USERS_TABLE_USER, array( 'account_status' => UACCOUNT_DEFAULT_FLAGS, 'username' => $username, 'email' => $email, 'authcode' => self::gen_auth(), 'hash' => self::crypt($password, $username) ) ); return $dbd->affected() ? true : false; } static protected function gen_auth($chars = 30) { $rchrs = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ0123456789'; for($rstr = '', $i = 1, $len = strlen($rchrs) - 1; $i <= $chars; $i++) $rstr .= substr($rchrs, mt_rand(0, $len), 1); return $rstr; } static public function crypt($text, $salt) { return md5(md5($text . strtolower($salt))); } const ID = 1, SESS = 0; static function cookiejar() { foreach($_COOKIE as $k => $v) { if(preg_match('/^__[a-z0-9]+$/i', $k) && is_numeric($v) && (strlen($k) == (UACCOUNT_AUTH_LEN + 2))) { return array($k, $v); break; } } return false; } } UExcept::register(array( user::ERR_EXISTS => 'The username you requested is already in use. Please, choose another.', user::ERR_INVALID_USER => 'The username you requested contains invalid characters. Please choose another. <i>(Acceptable characters include A-Z, 0-9, _, - and .)</i>', user::ERR_INVALID_PWD => 'Password must be atleast ' . UACCOUNT_PWDMIN . ' characters long.', user::ERR_INVALID_EMAIL => 'The email address you supplied is invalid.')); class user extends user_tools { public $data = array(); public $update = array(); public $error; protected $synced = false; protected $queried = false; public $cookie; public $id; // just to keep it outta the update array! static $dbd; const UFLG_ACCT_LOCK = 0x001, UFLG_ACCT_ROOT = 0x002; /* 0x004, 0x006, 0x008, 0x0010 */ public function __construct($user = null, $id = false) { // less flexibility more security... if(is_null($user) && !$id) { if($this->cookie()) return $this->__construct($this->cookie[parent::SESSION], true); } else { $this->data['username'] = $user; $this->update = array(); } if(!$this->sync($user, $id)) throw(new UExcept('', self::ERR_NOSUCHUSER)); } public function sync($user = null, $id = false) { if(($res = parent::retrieve(($user = is_null($user) ? $this->username : $user), $id))) { $this->update = array(); if(($this->data = $res->get())) { $this->id = $this->data['id']; return ($this->synced = $this->queried = true); } } throw(new UExcept('Unable to sync \'' . $this->username . '\'')); // security... } public function synced() { return $this->queried && $this->synced; } // this only exists incase I need to add/change conditions later.. public function locked() { if($this->status(self::UFLG_ACCT_LOCK)) { if(!$this->lock_expiry || $this->lock_expiry > time()) return true; $this->ulock(); } return false; } public function authenicate($pass, $auth = null) { // $pass is plaintext! if(empty($pass) || !isset($this->username)) throw(new UExcept('authenicate(); \'username\' or \'password\' undefined')); if(!$this->queried && !$this->sync()) return false; // this will never happen echo " lock: " . ($this->locked() ? 'yes' : 'no') . "<br />"; if($this->locked()) throw(new UExcept('This account has been locked, please check your email or consult site administration.', UExcept::LOCK)); if(($result = ( strlen($this->username) && !empty($this->hash) && $this->synced() && ($this->hash === parent::crypt($pass, $this->username)) ) ? true : false) === false ) { $this->attempts_failure = $this->attempts_failure + 1; echo "unf: " . $this->attempts_last . " expiry: " . ($this->attempts_last + UACCOUNT_ATTEMPTS_LOCK) . " > " . time() . "<br />"; // if(!$this->attempts_last) // do we have a record? if, no its the first.. if left to 0 it will always expire.. // $this->attempts_last = time(); if($this->attempts_last && ($this->attempts_last+UACCOUNT_ATTEMPTS_LOCK) > time()) { // has attempts time expired? if($this->attempts_failure >= UACCOUNT_ATTEMPTS_LIMIT) { $this->lock(UACCOUNT_ATTEMPTS_LOCK); return $this->authenicate($pass, $auth); // throw an exception at em } } elseif(!$this->attempts_last) { // it has expired, reset it $this->attempts_last = time(); $this->attempts_failure = 1; } } return $result; } public function active() { // do we have a good cookie! if(!$this->cookie()) return false; } // http://i140.photobucket.com/albums/r8/oatmeal_el/abortionomelets.jpg public function cookie($renew = false) { if($renew) { // find it. if(!$this->queried) return false; $this->logon_last = time(); if(!setcookie( UACCOUNT_COOKIE_CAP . ($this->authsess = parent::gen_auth()), $this->id, 0, UACCOUNT_COOKIE_PATH, UACCOUNT_COOKIE_DOMAIN, UACCOUNT_COOKIE_SSL, true) ) throw(new UExcept(__CLASS__ . '::cookie: Header output already began...')); $this->commit(); } else { if($cookie = $this->cookie ? $this->cookie : parent::cookiejar()) { if(($this->request_last < (time() - UACCOUNT_COOKIE_INACTIVE_EXPIRY)) && (substr($cookie[parent::SESSION], 2) === $this->authsess) ) $this->cookie = $cookie; else $this->remove_cookie($cookie[parent::SESSION]); // setcookie($cookie[parent::SESSION], '', time() - (60*60*24)); } echo substr($cookie[parent::SESSION], 2) . ' - ' . $this->authsess . '<br />'; $this->request_last = time(); } return $this->cookie; } public function remove_cookie($key = null) { if(!($key = ($key ? $key : $this->cookie[parent::SESSION]))) return false; return @setcookie( $key, $this->id, (time() - 60*60*24*30), UACCOUNT_COOKIE_PATH, UACCOUNT_COOKIE_DOMAIN, UACCOUNT_COOKIE_SSL, true ); } public function lock($expiry) { // do this as a method, then i can be updated with a commit() call, saving any additional queries aside from it echo "LOCKING!"; $this->lock_expiry = time() + $expiry; $this->authcode = parent::gen_auth(); $this->status(self::UFLG_ACCT_LOCK, '+'); $this->commit(); } public function ulock() { if(!$this->status(self::UFLG_ACCT_LOCK)) return false; echo "UNLOCKING!"; $this->lock_expiry = 0; $this->attempts_last = 0; $this->attempts_failure = 0; $this->status(self::UFLG_ACCT_LOCK, '-'); $this->commit(); } protected function status($flag = null, $op = null) { // $this->flags(FLAG, +|-) $stat = $this->account_status; if(is_null($flag)) return $stat; elseif($op == '-' && $this->status($flag)) $this->account_status = $stat - $flag; elseif($op == '+' && !$this->status($flag)) $this->account_status = $stat + $flag; return ($flag & $this->account_status); } // signal/modify/report more *normal* actions taken, those not affecting flow control public function __set($n, $v) { // if(isset($this->data[$n]) && $this->data[$n] === $v) // return;// echo " -- Setting data " . $n . " to '" . $v . "'<br />\n"; $this->synced = false; $this->update[$n] = 1; $this->data[$n] = $v; } public function __get($n) { if(isset($this->data[$n])) { // echo "Serving data: " . $n . " = " . $this->data[$n] . "<br />\n"; return $this->data[$n]; } throw(new UExcept('Requested user data, element \'' . $n .'\' undefined.')); // this will be annoying, but will force care to be taken with data - which is always a fine, fine idea } public function __isset($n) { return isset($this->data[$n]); } public function __unset($n) { $this->$n = null; } // dont do anything in this instance.. public function commit() { echo "COMMITING!"; global $dbd; if(empty($this->update) || !is_array($this->update)) return false; $bastard = array(); try { foreach($this->update as $k => $v) $bastard[$k] = $this->$k; } catch(UExcept $ux) { throw(new UExcept('It seems someone went and attached irrelevent data to the user object, stop that.. (' . $ux->getmessage() . ')' )); } $b = ; $this->update = array(); if(!empty($bastard)) return $dbd->exec( "UPDATE " . USERS_TABLE_USER . " " . "SET " . dbd::create_dstruct(dbd::UPDATE, $bastard) . " " . "WHERE id = '" . dbd::sanitize($this->id) . "'" ); } public function __destruct() { } public function logout() { $this->remove_cookie(); $this->authsess = ''; $this->__destruct(); } } class admin extends user { } ?>