From 395d435e227d0ef689a428bb6b740943efc63573 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Wed, 26 Mar 2014 10:54:33 -0500
Subject: [PATCH] Implement a remote user import process

This adds a feature for remote authentication methods for clients, such as
LDAP, which will, after successful authentication, yield a
ClientCreateRequest rather than an AuthenticatedUser. The
ClientCreateRequest represents a successful authentication and user
information lookup for a remote client. The client is then presented with a
registration page where their information for their account in the local
system can be reviewed prior to the account creation. Once created, the
client account is confirmed without an email confirmation and is logged in
immediately without reentering a password.
---
 account.php                     | 24 +++++++++++---
 include/class.auth.php          | 57 +++++++++++++++++++++++++++++++--
 include/class.client.php        |  6 ++++
 include/client/register.inc.php | 46 ++++++++++++++++++++++----
 login.php                       | 16 ++++++++-
 5 files changed, 135 insertions(+), 14 deletions(-)

diff --git a/account.php b/account.php
index d9c0793f9..7b8c7fa28 100644
--- a/account.php
+++ b/account.php
@@ -55,15 +55,13 @@ elseif ($_POST) {
     $user_form = UserForm::getUserForm()->getForm($_POST);
     if (!$user_form->isValid(function($f) { return !$f->get('internal'); }))
         $errors['err'] = 'Incomplete client information';
-    elseif (!$_POST['passwd1'])
+    elseif (!$_POST['backend'] && !$_POST['passwd1'])
         $errors['passwd1'] = 'New password required';
-    elseif ($_POST['passwd2'] != $_POST['passwd1'])
+    elseif (!$_POST['backend'] && $_POST['passwd2'] != $_POST['passwd1'])
         $errors['passwd1'] = 'Passwords do not match';
 
     // XXX: The email will always be in use already if a guest is logged in
     // and is registering for an account. Instead,
-    elseif (!($user = $thisclient ?: User::fromForm($user_form)))
-        $errors['err'] = 'Unable to register account. See messages below';
     elseif (($addr = $user_form->getField('email')->getClean())
             && ClientAccount::lookupByUsername($addr)) {
         $user_form->getField('email')->addError(
@@ -71,6 +69,12 @@ elseif ($_POST) {
             .urlencode($addr).'" style="color:inherit"><strong>sign in</strong></a>?');
         $errors['err'] = 'Unable to register account. See messages below';
     }
+    // Users created from ClientCreateRequest
+    elseif (isset($_POST['backend']) && !($user = User::fromVars($user_form->getClean())))
+        $errors['err'] = 'Unable to create local account. See messages below';
+    // New users and users registering from a ticket access link
+    elseif (!$user && !($user = $thisclient ?: User::fromForm($user_form)))
+        $errors['err'] = 'Unable to register account. See messages below';
     else {
         if (!($acct = ClientAccount::createForUser($user)))
             $errors['err'] = 'Internal error. Unable to create new account';
@@ -84,6 +88,18 @@ elseif ($_POST) {
             $content = Page::lookup(Page::getIdByType('registration-confirm'));
             $inc = 'register.confirm.inc.php';
             $acct->sendResetEmail('registration-client');
+            break;
+        case 'import':
+            foreach (UserAuthenticationBackend::allRegistered() as $bk) {
+                if ($bk::$id == $_POST['backend']) {
+                    $cl = new ClientSession(new EndUser($user));
+                    $acct->confirm();
+                    if ($user = $bk->login($cl, $bk))
+                        Http::redirect('tickets.php');
+                    break;
+                }
+            }
+            break;
         }
     }
 
diff --git a/include/class.auth.php b/include/class.auth.php
index 31459cd01..a1b8c5220 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -50,6 +50,44 @@ interface AuthDirectorySearch {
     function search($query);
 }
 
+/**
+ * Class: ClientCreateRequest
+ *
+ * Simple container to represent a remote authentication success for a
+ * client which should be imported into the local database. The class will
+ * provide access to the backend that authenticated the user, the username
+ * that the user entered when logging in, and any other information about
+ * the user that the backend was able to lookup. Generally, this extra
+ * information would be the same information retrieved from calling the
+ * AuthDirectorySearch::lookup() method.
+ */
+class ClientCreateRequest {
+
+    var $backend;
+    var $username;
+    var $info;
+
+    function __construct($backend, $username, $info=array()) {
+        $this->backend = $backend;
+        $this->username = $username;
+        $this->info = $info;
+    }
+
+    function getBackend() {
+        return $this->backend;
+    }
+    function setBackend($what) {
+        $this->backend = $what;
+    }
+
+    function getUsername() {
+        return $this->username;
+    }
+    function getInfo() {
+        return $this->info;
+    }
+}
+
 /**
  * Authentication backend
  *
@@ -133,6 +171,9 @@ abstract class AuthenticationBackend {
                 if ($result instanceof AuthenticatedUser
                         && ($bk->login($result, $bk)))
                     return $result;
+                elseif ($result instanceof ClientCreateRequest
+                        && $bk instanceof UserAuthenticationBackend)
+                    return $result;
                 elseif ($result instanceof AccessDenied) {
                     break;
                 }
@@ -407,8 +448,20 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
     }
 
     function getAllowedBackends($userid) {
-        // White listing backends for specific user not supported.
-        return array();
+        $backends = array();
+        $sql = 'SELECT A1.backend FROM '.USER_ACCOUNT_TABLE
+              .' A1 INNER JOIN '.USER_EMAIL_TABLE.' A2 ON (A2.user_id = A1.user_id)'
+              .' WHERE backend IS NOT NULL '
+              .' AND (A1.username='.db_input($userid)
+                  .' OR A2.`address`='.db_input($userid).')';
+
+        if (!($res=db_query($sql, false)))
+            return $backends;
+
+        while (list($bk) = db_fetch_row($res))
+            $backends[] = $bk;
+
+        return array_filter($backends);
     }
 
     function login($user, $bk) {
diff --git a/include/class.client.php b/include/class.client.php
index 8dfb211a8..7d1dfbcb7 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -474,6 +474,12 @@ class ClientAccount extends ClientAccountModel {
         $this->set('timezone_id', $vars['timezone_id']);
         $this->set('dst', isset($vars['dst']) ? 1 : 0);
 
+        if ($vars['backend']) {
+            $this->set('backend', $vars['backend']);
+            if ($vars['username'])
+                $this->set('username', $vars['username']);
+        }
+
         if ($vars['passwd1']) {
             $this->set('passwd', Passwd::hash($vars['passwd1']));
             $info = array('password' => $vars['passwd1']);
diff --git a/include/client/register.inc.php b/include/client/register.inc.php
index 71e54eb80..0a5676781 100644
--- a/include/client/register.inc.php
+++ b/include/client/register.inc.php
@@ -1,18 +1,31 @@
 <?php
-$info = $_POST ?: array(
-    'timezone_id' => $cfg->getDefaultTimezoneId(),
-    'dst' => $cfg->observeDaylightSaving(),
-);
+$info = $_POST;
+if (!isset($info['timezone_id']))
+    $info += array(
+        'timezone_id' => $cfg->getDefaultTimezoneId(),
+        'dst' => $cfg->observeDaylightSaving(),
+        'backend' => null,
+    );
+if (isset($user) && $user instanceof ClientCreateRequest) {
+    $bk = $user->getBackend();
+    $info = array_merge($info, array(
+        'backend' => $bk::$id,
+        'username' => $user->getUsername(),
+    ));
+}
+
 ?>
 <h1>Account Registration</h1>
 <p>
-Use the forms below to update the information we have on file for your
-account
+Use the forms below to create or update the information we have on file for
+your account
 </p>
 <form action="account.php" method="post">
   <?php csrf_token(); ?>
-  <input type="hidden" name="do" value="<?php echo $_REQUEST['do'] ?: 'create'; ?>" />
+  <input type="hidden" name="do" value="<?php echo $_REQUEST['do']
+    ?: ($info['backend'] ? 'import' :'create'); ?>" />
 <table width="800" class="padded">
+<tbody>
 <?php
     $cf = $user_form ?: UserForm::getInstance();
     $cf->render(false);
@@ -54,6 +67,23 @@ account
         <div><hr><h3>Access Credentials</h3></div>
     </td>
 </tr>
+<?php if ($info['backend']) { ?>
+<tr>
+    <td width="180">
+        Login With:
+    </td>
+    <td>
+        <input type="hidden" name="backend" value="<?php echo $info['backend']; ?>"/>
+        <input type="hidden" name="username" value="<?php echo $info['username']; ?>"/>
+<?php foreach (UserAuthenticationBackend::allRegistered() as $bk) {
+    if ($bk::$id == $info['backend']) {
+        echo $bk::$name;
+        break;
+    }
+} ?>
+    </td>
+</tr>
+<?php } else { ?>
 <tr>
     <td width="180">
         Create a Password:
@@ -72,6 +102,8 @@ account
         &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd2']; ?></span>
     </td>
 </tr>
+<?php } ?>
+</tbody>
 </table>
 <hr>
 <p style="text-align: center;">
diff --git a/login.php b/login.php
index 4d9bda78c..41658e483 100644
--- a/login.php
+++ b/login.php
@@ -35,7 +35,21 @@ if ($_POST && isset($_POST['luser'])) {
         $errors['err'] = 'Valid username or email address is required';
     elseif (($user = UserAuthenticationBackend::process($_POST['luser'],
             $_POST['lpasswd'], $errors))) {
-        Http::redirect($_SESSION['_client']['auth']['dest'] ?: 'tickets.php');
+        if ($user instanceof ClientCreateRequest) {
+            if ($cfg && $cfg->isClientRegistrationEnabled()) {
+                $inc = 'register.inc.php';
+                $user_form = UserForm::getUserForm()->getForm($user->getInfo());
+            }
+            else {
+                $errors['err'] = 'Access Denied. Contact your help desk
+                    administrator to have an account registered for you';
+                // fall through to show login page again
+            }
+        }
+        else {
+            Http::redirect($_SESSION['_client']['auth']['dest']
+                ?: 'tickets.php');
+        }
     } elseif(!$errors['err']) {
         $errors['err'] = 'Invalid username or password - try again!';
     }
-- 
GitLab