diff --git a/include/class.client.php b/include/class.client.php index 5c5f3035ee6b485d6862950b35924f00e0f90b97..087215ce7919e45331e5f6f813a7a56bd8e24156 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -4,7 +4,7 @@ Handles everything about client - NOTE: Please note that osTicket uses email address and ticket ID to authenticate the user*! + 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 . Peter Rotich <peter@osticket.com> @@ -134,72 +134,94 @@ class Client { return (($id=self::getLastTicketIdByEmail($email)))?self::lookup($id, $email):null; } - /* static */ function tryLogin($ticketID, $email, $auth=null) { + /* 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'; + $auto_login = ($_SERVER['REQUEST_METHOD'] == 'GET'); //Check time for last max failed login attempt strike. - $loginmsg='Invalid login'; - # XXX: SECURITY: Max attempts is enforced client-side via the PHP - # session cookie. if($_SESSION['_client']['laststrike']) { if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { - $loginmsg='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>'; - }else{ //Timeout is over. + $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; + $_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(!$errors && is_numeric($ticketID) && Validator::is_email($email) && ($ticket=Ticket::lookupByExtId($ticketID))) { - //At this point we know the ticket is valid. + 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 - if (!$auto_login || $auth === $ticket->getAuthToken()) { - if($ticket->getId() && strcasecmp($ticket->getEmail(),$email)==0){ - //valid match...create session goodies for the client. - $user = new ClientSession($email,$ticket->getId()); - $_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(); - //Log login info... - $msg=sprintf("%s/%s logged in [%s]",$ticket->getEmail(),$ticket->getExtId(),$_SERVER['REMOTE_ADDR']); - $ost->logDebug('User login', $msg); - //Redirect tickets.php - session_write_close(); - session_regenerate_id(); - @header("Location: tickets.php?id=".$ticket->getExtId()); - require_once('tickets.php'); //Just incase. of header already sent error. - exit; - } - } + + # 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(); + + //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->destroy($sid); + + session_write_close(); + + 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()) { - $loginmsg='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 client?'."\n". - 'Email: '.$_POST['lemail']."\n".'Ticket#: '.$_POST['lticket']."\n". + $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 (client)', $alert, ($cfg->alertONLoginError())); - }elseif($_SESSION['_client']['strikes']%2==0){ //Log every other failed login attempt as a warning. - $alert='Email: '.$_POST['lemail']."\n".'Ticket #: '.$_POST['lticket']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. + $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 (client)', $alert); + $ost->logWarning('Failed login attempt (user)', $alert); } + + return false; } } ?> diff --git a/include/class.staff.php b/include/class.staff.php index ace8f2a0b785c2b31d6fc0d490d0f21113fad9a4..778041e7ca6fd707d16b91b5f3a9a4669fe3076a 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -558,15 +558,21 @@ class Staff { if($_SESSION['_staff']['laststrike']) { if((time()-$_SESSION['_staff']['laststrike'])<$cfg->getStaffLoginTimeout()) { - $errors['err']='You\'ve reached maximum failed login attempts allowed.'; + $errors['err']='Max. failed login attempts reached'; + $_SESSION['_staff']['laststrike'] = time(); //reset timer. } else { //Timeout is over. //Reset the counter for next round of attempts after the timeout. $_SESSION['_staff']['laststrike']=null; $_SESSION['_staff']['strikes']=0; } } + + if(!$username || !$passwd) + $errors['err'] = 'Username and password required'; + + if($errors) return false; - if(!$errors && ($user=new StaffSession($username)) && $user->getId() && $user->check_passwd($passwd)) { + if(($user=new StaffSession(trim($username))) && $user->getId() && $user->check_passwd($passwd)) { //update last login && password reset stuff. $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; if($user->isPasswdResetDue() && !$user->isAdmin()) @@ -574,15 +580,18 @@ class Staff { $sql.=' WHERE staff_id='.db_input($user->getId()); db_query($sql); //Now set session crap and lets roll baby! - $_SESSION['_staff']=array(); //clear. - $_SESSION['_staff']['userID']=$username; + $_SESSION['_staff'] = array(); //clear. + $_SESSION['_staff']['userID'] = $username; $user->refreshSession(); //set the hash. - $_SESSION['TZ_OFFSET']=$user->getTZoffset(); - $_SESSION['TZ_DST']=$user->observeDaylight(); + $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); + $_SESSION['TZ_DST'] = $user->observeDaylight(); + //Log debug info. $ost->logDebug('Staff login', sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. - $sid=session_id(); //Current ID + + //Regenerate session id. + $sid=session_id(); //Current id session_regenerate_id(TRUE); //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. if(($session=$ost->getSession()) && is_object($session) && $sid) @@ -599,14 +608,14 @@ class Staff { $errors['err']='Forgot your login info? Contact Admin.'; $_SESSION['_staff']['laststrike']=time(); $alert='Excessive login attempts by a staff member?'."\n". - 'Username: '.$_POST['username']."\n".'IP: '.$_SERVER['REMOTE_ADDR']."\n".'TIME: '.date('M j, Y, g:i a T')."\n\n". + 'Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']."\n".'TIME: '.date('M j, Y, g:i a T')."\n\n". 'Attempts #'.$_SESSION['_staff']['strikes']."\n".'Timeout: '.($cfg->getStaffLoginTimeout()/60)." minutes \n\n"; - $ost->logWarning('Excessive login attempts ('.$_POST['username'].')', $alert, ($cfg->alertONLoginError())); + $ost->logWarning('Excessive login attempts ('.$username.')', $alert, ($cfg->alertONLoginError())); } elseif($_SESSION['_staff']['strikes']%2==0) { //Log every other failed login attempt as a warning. - $alert='Username: '.$_POST['username']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. + $alert='Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']. "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_staff']['strikes']; - $ost->logWarning('Failed staff login attempt ('.$_POST['username'].')', $alert, false); + $ost->logWarning('Failed staff login attempt ('.$username.')', $alert, false); } return false; diff --git a/include/class.ticket.php b/include/class.ticket.php index ec3dcdd1e54e6e075e071b5cc94e2092417bfb92..9f8ce96d5649c75c9a9035aad404e9aef39fdeb8 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1766,8 +1766,17 @@ class Ticket { /*============== Static functions. Use Ticket::function(params); ==================*/ - function getIdByExtId($extid) { - $sql ='SELECT ticket_id FROM '.TICKET_TABLE.' ticket WHERE ticketID='.db_input($extid); + function getIdByExtId($extId, $email=null) { + + if(!$extId || !is_numeric($extId)) + return 0; + + $sql ='SELECT ticket_id FROM '.TICKET_TABLE.' ticket ' + .' WHERE ticketID='.db_input($extId); + + if($email) + $sql.=' AND email='.db_input($email); + if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); @@ -1780,8 +1789,8 @@ class Ticket { return ($id && is_numeric($id) && ($ticket= new Ticket($id)) && $ticket->getId()==$id)?$ticket:null; } - function lookupByExtId($id) { - return self::lookup(self:: getIdByExtId($id)); + function lookupByExtId($id, $email=null) { + return self::lookup(self:: getIdByExtId($id, $email)); } function genExtRandID() { diff --git a/include/client/login.inc.php b/include/client/login.inc.php index e1e52e9d454e7a1a4f62fdfec39c5625e496c5d2..7e46c2eb30598e2220128654b0232cd08d81ac62 100644 --- a/include/client/login.inc.php +++ b/include/client/login.inc.php @@ -1,5 +1,5 @@ <?php -if(!defined('OSTCLIENTINC')) die('Kwaheri'); +if(!defined('OSTCLIENTINC')) die('Access Denied'); $email=Format::input($_POST['lemail']?$_POST['lemail']:$_GET['e']); $ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']); @@ -8,7 +8,8 @@ $ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']); <p>To view the status of a ticket, provide us with the login details below.</p> <form action="login.php" method="post" id="clientLogin"> <?php csrf_token(); ?> - <strong>Authentication Required</strong> + <strong><?php echo Format::htmlchars($errors['login']); ?></strong> + <br> <div> <label for="email">E-Mail Address:</label> <input id="email" type="text" name="lemail" size="30" value="<?php echo $email; ?>"> diff --git a/include/staff/login.tpl.php b/include/staff/login.tpl.php index 2b4d21ad015f1e2d6124c8a3422572bcce53d91e..b8b136eb56d01282e8b15dbd4c0f11085e437eaa 100644 --- a/include/staff/login.tpl.php +++ b/include/staff/login.tpl.php @@ -1,4 +1,8 @@ -<?php defined('OSTSCPINC') or die('Invalid path'); ?> +<?php +defined('OSTSCPINC') or die('Invalid path'); + +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> @@ -22,9 +26,9 @@ <h3><?php echo Format::htmlchars($msg); ?></h3> <form action="login.php" method="post"> <?php csrf_token(); ?> - <input type="hidden" name="d"o value="scplogin"> + <input type="hidden" name="do" value="scplogin"> <fieldset> - <input type="text" name="username" id="name" value="" placeholder="username" autocorrect="off" autocapitalize="off"> + <input type="text" name="username" id="name" value="<?php echo $info['username']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> <input type="password" name="passwd" id="pass" placeholder="password" autocorrect="off" autocapitalize="off"> </fieldset> <input class="submit" type="submit" name="submit" value="Log In"> diff --git a/login.php b/login.php index f35693fd4083401a1aea72125e8cb1cc116710c7..834f00ddf72410de113271462f272ef38a4b9737 100644 --- a/login.php +++ b/login.php @@ -21,8 +21,17 @@ define('OSTCLIENTINC',TRUE); //make includes happy require_once(INCLUDE_DIR.'class.client.php'); require_once(INCLUDE_DIR.'class.ticket.php'); -if ($_POST) ClientSession::tryLogin($_POST['lticket'], $_POST['lemail']); -else ClientSession::tryLogin($_GET['t'], $_GET['e'], $_GET['a']); +if($_POST) { + + if(($user=Client::login(trim($_POST['lticket']), trim($_POST['lemail']), null, $errors))) { + //XXX: Ticket owner is assumed. + @header('Location: tickets.php?id='.$user->getTicketID()); + require_once('tickets.php'); //Just in case of 'header already sent' error. + exit; + } elseif(!$errors['err']) { + $errors['err'] = 'Authentication error - try again!'; + } +} $nav = new UserNav(); $nav->setActiveNav('status'); diff --git a/main.inc.php b/main.inc.php index 872f56758854a2d84ba78251dcce13c25cc00a42..b007a5a4b53fbe43cb20967c323da94721311a4b 100644 --- a/main.inc.php +++ b/main.inc.php @@ -112,7 +112,8 @@ require(INCLUDE_DIR.'mysql.php'); #CURRENT EXECUTING SCRIPT. - define('THISPAGE',Misc::currentURL()); + define('THISPAGE', Misc::currentURL()); + define('THISURI', $_SERVER['REQUEST_URI']); # This is to support old installations. with no secret salt. if(!defined('SECRET_SALT')) define('SECRET_SALT',md5(TABLE_PREFIX.ADMIN_EMAIL)); diff --git a/scp/login.php b/scp/login.php index 6a28e0f45130f26f733f8eca82066565f5e2d9dc..5ba5e28e0568afb7626001cc00d76d35b02f2f93 100644 --- a/scp/login.php +++ b/scp/login.php @@ -19,20 +19,19 @@ if(!defined('INCLUDE_DIR')) die('Fatal Error. Kwaheri!'); require_once(INCLUDE_DIR.'class.staff.php'); require_once(INCLUDE_DIR.'class.csrf.php'); -$msg=$_SESSION['_staff']['auth']['msg']; -$msg=$msg?$msg:'Authentication Required'; -if($_POST && (!empty($_POST['username']) && !empty($_POST['passwd']))){ +$dest = $_SESSION['_staff']['auth']['dest']; +$msg = $_SESSION['_staff']['auth']['msg']; +$msg = $msg?$msg:'Authentication Required'; +if($_POST) { //$_SESSION['_staff']=array(); #Uncomment to disable login strikes. - $msg='Invalid login'; - if(($user=Staff::login($_POST['username'],$_POST['passwd'],$errors))){ - $dest=$_SESSION['_staff']['auth']['dest']; + if(($user=Staff::login($_POST['username'], $_POST['passwd'], $errors))){ $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; @header("Location: $dest"); require_once('index.php'); //Just incase header is messed up. exit; - }elseif(!$errors['err']){ - $errors['err']='Login error - try again'; } + + $msg = $errors['err']?$errors['err']:'Invalid login'; } define("OSTSCPINC",TRUE); //Make includes happy! include_once(INCLUDE_DIR.'staff/login.tpl.php'); diff --git a/scp/staff.inc.php b/scp/staff.inc.php index 5dcf6045042397c1ee806a8df9f0f2b0f28725a3..decf2c5cd624a719f718d5e53e92770fc502ae4c 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -49,7 +49,7 @@ require_once(INCLUDE_DIR.'class.csrf.php'); if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the function to trap expired sessions. function staffLoginPage($msg) { - $_SESSION['_staff']['auth']['dest']=THISPAGE; + $_SESSION['_staff']['auth']['dest']=THISURI; $_SESSION['_staff']['auth']['msg']=$msg; require(SCP_DIR.'login.php'); exit; diff --git a/view.php b/view.php index 984b04c3019645c6ca1d075bbab0aac8460e4094..10e5374fe71b8dcf3551c0ee922ac5b4800e961a 100644 --- a/view.php +++ b/view.php @@ -3,6 +3,7 @@ view.php Ticket View. + TODO: Support different views based on auth_token - e.g for BCC'ed users vs. Ticket owner. Peter Rotich <peter@osticket.com> Copyright (c) 2006-2010 osTicket @@ -14,8 +15,22 @@ vim: expandtab sw=4 ts=4 sts=4: $Id: $ **********************************************************************/ -require('secure.inc.php'); -if(!is_object($thisclient) || !$thisclient->isValid()) die('Access denied'); //Double check again. -//We are now using tickets.php but we need to keep view.php for backward compatibility +require_once('client.inc.php'); + +//If the user is NOT logged in - try auto-login (if params exists). +if(!$thisclient || !$thisclient->isValid()) { + // * On login Client::login will redirect the user to tickets.php view. + // * See TODO above for planned multi-view. + $user = null; + if($_GET['t'] && $_GET['e'] && $_GET['a']) + $user = Client::login($_GET['t'], $_GET['e'], $_GET['a'], $errors); + + //XXX: For now we're assuming the user is the ticket owner + // (multi-view based on auth token will come later). + if($user && $user->getTicketID()==trim($_GET['t'])) + @header('Location: tickets.php?id='.$user->getTicketID()); +} + +//Simply redirecting to tickets.php until multiview is implemented. require('tickets.php'); ?>