diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index c4bd447e8ce924d267453b60b4b0faa764ce5a5f..e7056b813e47b2d0e95548fd77f1e35ac4b352cc 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -634,7 +634,7 @@ label.required { } #clientLogin p { clear: both; - text-align: center; + text-align: right; } #clientLogin strong { font-size: 11px; diff --git a/bootstrap.php b/bootstrap.php index 0a50c4a2635b693e575d87a3a13dda9f66e3a997..c3a02a92f41681f1c489443aaa39b4a23a2d8833 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -70,6 +70,7 @@ class Bootstrap { define('ATTACHMENT_TABLE',$prefix.'attachment'); define('USER_TABLE',$prefix.'user'); define('USER_EMAIL_TABLE',$prefix.'user_email'); + define('USER_ACCOUNT_TABLE',$prefix.'user_account'); define('STAFF_TABLE',$prefix.'staff'); define('TEAM_TABLE',$prefix.'team'); diff --git a/include/class.auth.php b/include/class.auth.php index 660acf9ba2aba5946acd40fd2ec07bedea154237..9e981d0119df2378d00b6fe27f302ba4318697d4 100644 --- a/include/class.auth.php +++ b/include/class.auth.php @@ -797,38 +797,41 @@ class AuthTokenAuthentication extends UserAuthenticationBackend { } UserAuthenticationBackend::register('AuthTokenAuthentication'); -//Simple ticket lookup backend used to recover ticket access link. -// We're using authentication backend so we can guard aganist brute force -// attempts (which doesn't buy much since the link is emailed) -class AccessLinkAuthentication extends UserAuthenticationBackend { - static $name = "Ticket Access Link Authentication"; - static $id = "authlink"; - - function authenticate($email, $number) { - - if (!($ticket = Ticket::lookupByNumber($number)) - || !($user=User::lookup(array('emails__address' => - $email)))) - return false; +class osTicketClientAuthentication extends UserAuthenticationBackend { + static $name = "Local Client Authentication"; + static $id = "client"; - //Ticket owner? - if ($ticket->getUserId() == $user->getId()) - $user = $ticket->getOwner(); - //Collaborator? - elseif (!($user = Collaborator::lookup(array('userId' => - $user->getId(), 'ticketId' => - $ticket->getId())))) - return false; //Bro, we don't know you! + function authenticate($username, $password) { + if (strpos($username, '@') !== false) + $user = User::lookup(array('emails__address'=>$username)); + else + $user = User::lookup(array('account__username'=>$username)); + if (!$user) + return; - return new ClientSession($user); + if (($client = new ClientSession(new EndUser($user))) + && $client->getId() + && ($acct = $client->getAccount()) + && $acct->checkPassword($password) + ) { + return $client; + } } - //We are not actually logging in the user.... - function login($user, $bk) { - return true; + protected function validate($authkey) { + if (strpos($authkey, '@') !== false) + $user = User::lookup(array('emails__address'=>$authkey)); + else + $user = User::lookup(array('account__authkey'=>$authkey)); + + if (!$user) + return; + + if (($client = new ClientSession(new EndUser($user))) && $client->getId()) + return $client; } } -UserAuthenticationBackend::register('AccessLinkAuthentication'); +UserAuthenticationBackend::register('osTicketClientAuthentication'); ?> diff --git a/include/class.client.php b/include/class.client.php index c9f6fc394ec8fa53619d2d1b2867dfdccbb3db3d..5af39ac61b5f4a2c9b6ecead082fb31ee7f469bd 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -187,7 +187,10 @@ class EndUser extends AuthenticatedUser { if ($this->user instanceof Collaborator) return $this->user->getUserId(); - return $this->user->getId(); + elseif ($this->user) + return $this->user->getId(); + + return false; } function getUserName() { @@ -225,6 +228,13 @@ class EndUser extends AuthenticatedUser { return ($stats=$this->getTicketStats())?$stats['closed']:0; } + function hasAccount() { + } + + function getAccount() { + return ClientAccount::lookup(array('user_id'=>$this->getId())); + } + private function getStats() { $sql='SELECT count(open.ticket_id) as open, count(closed.ticket_id) as closed ' @@ -242,4 +252,51 @@ class EndUser extends AuthenticatedUser { return db_fetch_array(db_query($sql)); } } + +require_once INCLUDE_DIR.'class.orm.php'; +class ClientAccountModel extends VerySimpleModel { + static $meta = array( + 'table' => USER_ACCOUNT_TABLE, + 'pk' => 'id', + 'joins' => array( + 'user' => array( + 'null' => false, + 'constraint' => array('user_id' => 'UserModel.id') + ), + ), + ); +} + +class ClientAccount extends ClientAccountModel { + var $_options = null; + + const LOCKED = 0x0001; + const PASSWD_RESET_REQUIRED = 0x0002; + + function checkPassword($password, $autoupdate=true) { + + /*bcrypt based password match*/ + if(Passwd::cmp($password, $this->get('passwd'))) + return true; + + //Fall back to MD5 + if(!$password || strcmp($this->get('passwd'), MD5($password))) + return false; + + //Password is a MD5 hash: rehash it (if enabled) otherwise force passwd change. + if ($autoupdate) + $this->set('passwd', Passwd::hash($password)); + + if (!$autoupdate || !$this->save()) + $this->forcePasswdReset(); + + return true; + } + + function forcePasswdReset() { + $this->set('status', $this->get('status') | self::PASSWD_RESET_REQUIRED); + $this->save(); + } +} + ?> diff --git a/include/class.orm.php b/include/class.orm.php index b9bda6a1176ac4beea1aee9301360e50328a315b..951a4ffd425e88109f35c103aeed6e425bd2fb52 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -113,7 +113,8 @@ class VerySimpleModel { $constraint[$field] = "$model.$foreign"; } $j['constraint'] = $constraint; - $j['list'] = true; + if (!isset($j['list'])) + $j['list'] = true; } // XXX: Make this better (ie. composite keys) $keys = array_keys($j['constraint']); diff --git a/include/class.user.php b/include/class.user.php index 0a945f68b57c1b37ed93c59fa40fbd7b5ba45969..c5b0f5b9e47b67bbf8027cd8af15bc583ea4c7da 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -36,6 +36,10 @@ class UserModel extends VerySimpleModel { 'emails' => array( 'reverse' => 'UserEmailModel.user', ), + 'account' => array( + 'list' => false, + 'reverse' => 'ClientAccountModel.user', + ), 'default_email' => array( 'null' => true, 'constraint' => array('default_email_id' => 'UserEmailModel.id') diff --git a/include/client/login.inc.php b/include/client/login.inc.php index 586ef9808b75144e37dafc775debb9cc8f4ca1c4..9ed334e951094f02cb173fec38fa7106b1e2a728 100644 --- a/include/client/login.inc.php +++ b/include/client/login.inc.php @@ -1,26 +1,24 @@ <?php if(!defined('OSTCLIENTINC')) die('Access Denied'); -$email=Format::input($_POST['lemail']?$_POST['lemail']:$_GET['e']); -$ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']); +$email=Format::input($_POST['luser']?:$_GET['e']); +$passwd=Format::input($_POST['lpasswd']?:$_GET['t']); ?> -<h1>Check Ticket Status</h1> -<p>Please provide us with your email address and a ticket number, and an access -link will be emailed to you.</p> +<h1>Sign In</h1> <form action="login.php" method="post" id="clientLogin"> <?php csrf_token(); ?> <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; ?>"> + <label for="username">Username:</label> + <input id="username" type="text" name="luser" size="30" value="<?php echo $email; ?>"> </div> <div> - <label for="ticketno">Ticket Number:</label> - <input id="ticketno" type="text" name="lticket" size="16" value="<?php echo $ticketid; ?>"></td> + <label for="passwd">Password:</label> + <input id="passwd" type="password" name="lpasswd" size="30" value="<?php echo $passwd; ?>"></td> </div> <p> - <input class="btn" type="submit" value="Email Access Link"> + <input class="btn" type="submit" value="Sign In"> </p> </form> <br> diff --git a/login.php b/login.php index 9e6d5f36f18d64384f96818ae82498a774553344..4751f74976b550827221ebcf7292b1d5b72bda24 100644 --- a/login.php +++ b/login.php @@ -26,15 +26,11 @@ require_once(INCLUDE_DIR.'class.ticket.php'); $inc = 'login.inc.php'; if ($_POST) { - if (!$_POST['lticket'] || !Validator::is_email($_POST['lemail'])) - $errors['err'] = 'Valid email address and ticket number required'; - elseif (($user = UserAuthenticationBackend::process($_POST['lemail'], - $_POST['lticket'], $errors))) { - //We're using authentication backend so we can guard aganist brute - // force attempts (which doesn't buy much since the link is emailed) - $user->sendAccessLink(); - $msg = sprintf("%s - access link sent to your email!", - $user->getName()->getFirst()); + if (!$_POST['luser']) + $errors['err'] = 'Valid username or email address is required'; + elseif (($user = UserAuthenticationBackend::process($_POST['luser'], + $_POST['lpasswd'], $errors))) { + Http::redirect('tickets.php'); $_POST = null; } elseif(!$errors['err']) { $errors['err'] = 'Invalid email or ticket number - try again!';