From 2c83e037bceba37a74d5ed03bfc97745723fbf3c Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Wed, 19 Mar 2014 13:08:26 -0500 Subject: [PATCH] First steps for client login --- assets/default/css/theme.css | 2 +- bootstrap.php | 1 + include/class.auth.php | 55 +++++++++++++++++---------------- include/class.client.php | 59 +++++++++++++++++++++++++++++++++++- include/class.orm.php | 3 +- include/class.user.php | 4 +++ include/client/login.inc.php | 18 +++++------ login.php | 14 +++------ 8 files changed, 108 insertions(+), 48 deletions(-) diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index c4bd447e8..e7056b813 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 0a50c4a26..c3a02a92f 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 660acf9ba..9e981d011 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 c9f6fc394..5af39ac61 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 b9bda6a11..951a4ffd4 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 0a945f68b..c5b0f5b9e 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 586ef9808..9ed334e95 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 9e6d5f36f..4751f7497 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!'; -- GitLab