diff --git a/include/class.client.php b/include/class.client.php index 486cf6e74576d3d6581d2b729eb3bb8939b74955..2e8f6e32ccb26a0c2cf931bf01a04ea6bec2052b 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -2,10 +2,7 @@ /********************************************************************* class.client.php - Handles everything about client - - XXX: Please note that osTicket uses email address and ticket ID to authenticate the user*! - Client is modeled on the info of the ticket used to login . + Handles everything about EndUser Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket @@ -16,229 +13,117 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +abstract class TicketUser { -class Client { - - var $id; - var $fullname; - var $username; - var $email; - - var $_answers; - - var $ticket_id; - var $ticketID; - - var $ht; - - var $user; - - - function Client($id, $email=null) { - $this->id =0; - $this->load($id,$email); - } - - function load($id=0, $email=null) { - - if(!$id && !($id=$this->getId())) - return false; - - $sql='SELECT ticket.ticket_id, ticketID, email.address as email ' - .' FROM '.TICKET_TABLE.' ticket ' - .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id' - .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id' - .' WHERE ticketID='.db_input($id); - - if($email) - $sql.=' AND email.address = '.db_input($email); - - if(!($res=db_query($sql)) || !db_num_rows($res)) - return NULL; - - $this->ht = db_fetch_array($res); - $this->id = $this->ht['ticketID']; //placeholder - $this->ticket_id = $this->ht['ticket_id']; - $this->ticketID = $this->ht['ticketID']; + static private $token_regex = '/^(?P<type>\w{1})(?P<id>\d+)t(?P<tid>\d+)x(?P<algo>\d+)h(?P<hash>.*)$/i'; - $this->user = User::lookup(array('emails__address'=>$this->ht['email'])); - $this->fullname = $this->user->getFullName(); - $this->username = $this->ht['email']; - $this->email = $this->ht['email']; - - $this->stats = array(); - - return($this->id); - } - - function loadDynamicData() { - $this->_answers = array(); - foreach (DynamicFormEntry::forClient($this->getId()) as $form) - foreach ($form->getAnswers() as $answer) - $this->_answers[$answer->getField()->get('name')] = - $answer->getValue(); - } + protected $user; - function reload() { - return $this->load(); + function __construct($user) { + $this->user = $user; } - function isClient() { - return TRUE; - } + function __call($name, $args) { + global $cfg; + + if($this->user && is_callable(array($this->user, $name))) + return $args + ? call_user_func_array(array($this->user, $name), $args) + : call_user_func(array($this->user, $name)); + + $tag = substr($name, 3); + switch (strtolower($tag)) { + case 'ticket_link': + return sprintf('%s/view.php?auth=%s', + $cfg->getBaseUrl(), + urlencode($this->getAuthToken())); + break; + } - function getId() { - return $this->id; - } + return false; - function getEmail() { - return $this->email; } - function getUserName() { - return $this->username; - } + protected function getAuthToken($type=1) { - function getName() { - return $this->fullname; - } + //Format: // c<id>x<algo id used>h<hash for algo> + $authtoken = ''; + switch($type) { + case 1: + $authtoken = sprintf('%s%dt%dx%dh%s', + ($this->isOwner() ? 'o' : 'c'), + $this->getId(), + $this->getTicketId(), + $type, + substr(base64_encode(md5($this->getId().$this->getTicket()->getCreateDate().$this->getTicketId().SECRET_SALT, + true)), 16)); + break; + } - function getPhone() { - return $this->_answers['phone']; - } + //TODO: Throw an exception + if (!$authtoken) + return false; - function getTicketID() { - return $this->ticketID; - } + return $authtoken; + } + + static function lookupByToken($token) { + + //Expecting well formatted token see getAuthToken routine for details. + $matches = array(); + if (!preg_match(static::$token_regex, $token, $matches)) + return null; + + $user = null; + switch ($matches['type']) { + case 'c': //Collaborator c + if (($user = Collaborator::lookup($matches['id'])) + && $user->getTicketId() != $matches['tid']) + $user = null; + break; + case 'o': //Ticket owner + if (($ticket = Ticket::lookup($matches['tid']))) { + if (($user = $ticket->getOwner()) + && $user->getId() != $matches['id']) + $user = null; + } + break; + } - function getTicketStats() { + if (!$user + || !$user instanceof TicketUser + || strcasecmp($user->getAuthToken($matches['algo']), $token)) + return false; - if(!$this->stats['tickets']) - $this->stats['tickets'] = Ticket::getClientStats($this->getEmail()); + var_dump($user); - return $this->stats['tickets']; + return $user; } - function getNumTickets() { - return ($stats=$this->getTicketStats())?($stats['open']+$stats['closed']):0; + function isOwner() { + return ($this->user + && $this->user->getId() == $this->getTicket()->getOwnerId()); } - function getNumOpenTickets() { - return ($stats=$this->getTicketStats())?$stats['open']:0; - } + abstract function getTicketId(); + abstract function getTicket(); +} - function getNumClosedTickets() { - return ($stats=$this->getTicketStats())?$stats['closed']:0; - } +class TicketOwner extends TicketUser { - /* ------------- Static ---------------*/ - function getLastTicketIdByEmail($email) { - $sql='SELECT ticket.ticketID FROM '.TICKET_TABLE.' ticket ' - .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id' - .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id' - .' WHERE email.address = '.db_input($email) - .' ORDER BY ticket.created ' - .' LIMIT 1'; - if(($res=db_query($sql)) && db_num_rows($res)) - list($tid) = db_fetch_row($res); - - return $tid; - } + protected $ticket; - function lookup($id, $email=null) { - return ($id && is_numeric($id) && ($c=new Client($id,$email)) && $c->getId()==$id)?$c:null; + function __construct($user, $ticket) { + parent::__construct($user); + $this->ticket = $ticket; } - function lookupByEmail($email) { - return (($id=self::getLastTicketIdByEmail($email)))?self::lookup($id, $email):null; + function getTicket() { + return $this->ticket; } - /* static */ function login($ticketID, $email, $auth=null, &$errors=array()) { - global $ost; - - - $cfg = $ost->getConfig(); - $auth = trim($auth); - $email = trim($email); - $ticketID = trim($ticketID); - - # Only consider auth token for GET requests, and for GET requests, - # REQUIRE the auth token - $auto_login = ($_SERVER['REQUEST_METHOD'] == 'GET'); - - //Check time for last max failed login attempt strike. - if($_SESSION['_client']['laststrike']) { - if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { - $errors['login'] = 'Excessive failed login attempts'; - $errors['err'] = 'You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>'; - $_SESSION['_client']['laststrike'] = time(); //renew the strike. - } else { //Timeout is over. - //Reset the counter for next round of attempts after the timeout. - $_SESSION['_client']['laststrike'] = null; - $_SESSION['_client']['strikes'] = 0; - } - } - - if($auto_login && !$auth) - $errors['login'] = 'Invalid method'; - elseif(!$ticketID || !Validator::is_email($email)) - $errors['login'] = 'Valid email and ticket number required'; - - //Bail out on error. - if($errors) return false; - - //See if we can fetch local ticket id associated with the ID given - if(($ticket=Ticket::lookupByExtId($ticketID, $email)) && $ticket->getId()) { - //At this point we know the ticket ID is valid. - //TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets?? - //Check the email given. - - # Require auth token for automatic logins (GET METHOD). - if (!strcasecmp($ticket->getEmail(), $email) && (!$auto_login || $auth === $ticket->getAuthToken())) { - - //valid match...create session goodies for the client. - $user = new ClientSession($email,$ticket->getExtId()); - $_SESSION['_client'] = array(); //clear. - $_SESSION['_client']['userID'] = $ticket->getEmail(); //Email - $_SESSION['_client']['key'] = $ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. - $_SESSION['_client']['token'] = $user->getSessionToken(); - $_SESSION['TZ_OFFSET'] = $cfg->getTZoffset(); - $_SESSION['TZ_DST'] = $cfg->observeDaylightSaving(); - $user->refreshSession(); //set the hash. - //Log login info... - $msg=sprintf('%s/%s logged in [%s]', $ticket->getEmail(), $ticket->getExtId(), $_SERVER['REMOTE_ADDR']); - $ost->logDebug('User login', $msg); - - //Regenerate session ID. - $sid=session_id(); //Current session id. - session_regenerate_id(TRUE); //get new ID. - if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) - $session->destroy($sid); - - return $user; - - } - } - - //If we get to this point we know the login failed. - $errors['login'] = 'Invalid login'; - $_SESSION['_client']['strikes']+=1; - if(!$errors && $_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) { - $errors['login'] = 'Access Denied'; - $errors['err'] = 'Forgot your login info? Please <a href="open.php">open a new ticket</a>.'; - $_SESSION['_client']['laststrike'] = time(); - $alert='Excessive login attempts by a user.'."\n". - 'Email: '.$email."\n".'Ticket#: '.$ticketID."\n". - 'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n". - 'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logError('Excessive login attempts (user)', $alert, ($cfg->alertONLoginError())); - } elseif($_SESSION['_client']['strikes']%2==0) { //Log every other failed login attempt as a warning. - $alert='Email: '.$email."\n".'Ticket #: '.$ticketID."\n".'IP: '.$_SERVER['REMOTE_ADDR']. - "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logWarning('Failed login attempt (user)', $alert); - } - - return false; + function getTicketId() { + return $this->ticket->getId(); } } @@ -260,7 +145,6 @@ class EndUser extends AuthenticatedUser { */ function __call($name, $args) { - if(!$this->user || !is_callable(array($this->user, $name))) return false; @@ -278,10 +162,6 @@ class EndUser extends AuthenticatedUser { return $this->user->getId(); } - function isOwner() { - return ($this->user && $this->user instanceof Client); - } - function getUserName() { //XXX: Revisit when real usernames are introduced or when email // requirement is removed. @@ -296,6 +176,43 @@ class EndUser extends AuthenticatedUser { return UserAuthenticationBackend::signOut($this); } -} + function getTicketStats() { + + if (!isset($this->ht['stats'])) + $this->ht['stats'] = $this->getStats(); + return $this->ht['stats']; + } + + function getNumTickets() { + return ($stats=$this->getTicketStats())?($stats['open']+$stats['closed']):0; + } + + function getNumOpenTickets() { + return ($stats=$this->getTicketStats())?$stats['open']:0; + } + + function getNumClosedTickets() { + return ($stats=$this->getTicketStats())?$stats['closed']:0; + } + + private function getStats() { + + $sql='SELECT count(open.ticket_id) as open, count(closed.ticket_id) as closed ' + .' FROM '.TICKET_TABLE.' ticket ' + .' LEFT JOIN '.TICKET_TABLE.' open + ON (open.ticket_id=ticket.ticket_id AND open.status=\'open\') ' + .' LEFT JOIN '.TICKET_TABLE.' closed + ON (closed.ticket_id=ticket.ticket_id AND closed.status=\'closed\')' + .' LEFT JOIN '.TICKET_COLLABORATOR_TABLE.' collab + ON (collab.ticket_id=ticket.ticket_id + AND collab.user_id = '.db_input($this->getId()).' )' + .' WHERE ticket.user_id = '.db_input($this->getId()) + .' OR collab.user_id = '.db_input($this->getId()); + + return db_fetch_array(db_query($sql)); + } + + +} ?>