]> BookStack Code Mirror - bookstack/commitdiff
Got LDAP auth working to a functional state
authorDan Brown <redacted>
Mon, 11 Jan 2016 22:41:05 +0000 (22:41 +0000)
committerDan Brown <redacted>
Mon, 11 Jan 2016 22:41:05 +0000 (22:41 +0000)
app/Http/Controllers/Auth/AuthController.php
app/Http/routes.php
app/Providers/AuthServiceProvider.php
app/Providers/LdapUserProvider.php
app/Repos/UserRepo.php
app/Role.php
app/Services/LdapService.php
config/auth.php
database/migrations/2016_01_11_210908_add_external_auth_to_users.php [new file with mode: 0644]
resources/views/auth/forms/login/ldap.blade.php

index c86e0d78991cf60cd24656a0f17b7d70b9802098..98ef67987ce6960b243e7ce306d86010269b6e4e 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
+use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Http\Request;
 use BookStack\Exceptions\SocialSignInException;
 use BookStack\Exceptions\UserRegistrationException;
@@ -31,6 +32,8 @@ class AuthController extends Controller
 
     protected $redirectPath = '/';
     protected $redirectAfterLogout = '/login';
+    protected $username = 'email';
+
 
     protected $socialAuthService;
     protected $emailConfirmationService;
@@ -48,6 +51,7 @@ class AuthController extends Controller
         $this->socialAuthService = $socialAuthService;
         $this->emailConfirmationService = $emailConfirmationService;
         $this->userRepo = $userRepo;
+        $this->username = config('auth.method') === 'standard' ? 'email' : 'username';
         parent::__construct();
     }
 
@@ -104,6 +108,24 @@ class AuthController extends Controller
         return $this->registerUser($userData);
     }
 
+
+    /**
+     * Overrides the action when a user is authenticated.
+     * If the user authenticated but does not exist in the user table we create them.
+     * @param Request         $request
+     * @param Authenticatable $user
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    protected function authenticated(Request $request, Authenticatable $user)
+    {
+        if(!$user->exists) {
+            $user->save();
+            $this->userRepo->attachDefaultRole($user);
+            auth()->login($user);
+        }
+        return redirect()->intended($this->redirectPath());
+    }
+
     /**
      * Register a new user after a registration callback.
      * @param $socialDriver
@@ -232,7 +254,7 @@ class AuthController extends Controller
     public function getLogin()
     {
         $socialDrivers = $this->socialAuthService->getActiveDrivers();
-        $authMethod = 'standard'; // TODO - rewrite to use config.
+        $authMethod = config('auth.method');
         return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
     }
 
index a8eb6b6e45b693838a3cad144df50a3673431ab1..aedfca9bce9521f23741df2933dde82aae0669a1 100644 (file)
@@ -3,7 +3,7 @@
 Route::get('/test', function() {
     // TODO - remove this
     $service = new \BookStack\Services\LdapService();
-    $service->getUserDetails('ssmith');
+    dd($service->getUserDetails('ksmith'));
 });
 
 // Authenticated routes...
index 40e94b0160a300996beb2f8de6abf5678533a78f..c027578a775a3d69eb6770d96e50c7caa40288de 100644 (file)
@@ -25,7 +25,7 @@ class AuthServiceProvider extends ServiceProvider
     public function register()
     {
         Auth::provider('ldap', function($app, array $config) {
-            return new LdapUserProvider($config['model']);
+            return new LdapUserProvider($config['model'], $app['BookStack\Services\LdapService']);
         });
     }
 }
index c2b961a34a2489e5ac9fa9c21ccccfbbb79788a3..407791a7d0a555ef1c9ece628a89c5ecef599557 100644 (file)
@@ -3,6 +3,8 @@
 namespace BookStack\Providers;
 
 
+use BookStack\Role;
+use BookStack\Services\LdapService;
 use BookStack\User;
 use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Contracts\Auth\UserProvider;
@@ -17,14 +19,21 @@ class LdapUserProvider implements UserProvider
      */
     protected $model;
 
+    /**
+     * @var LdapService
+     */
+    protected $ldapService;
+
 
     /**
      * LdapUserProvider constructor.
-     * @param $model
+     * @param             $model
+     * @param LdapService $ldapService
      */
-    public function __construct($model)
+    public function __construct($model, LdapService $ldapService)
     {
         $this->model = $model;
+        $this->ldapService = $ldapService;
     }
 
     /**
@@ -34,8 +43,7 @@ class LdapUserProvider implements UserProvider
      */
     public function createModel()
     {
-        $class = '\\'.ltrim($this->model, '\\');
-
+        $class = '\\' . ltrim($this->model, '\\');
         return new $class;
     }
 
@@ -55,7 +63,7 @@ class LdapUserProvider implements UserProvider
      * Retrieve a user by their unique identifier and "remember me" token.
      *
      * @param  mixed  $identifier
-     * @param  string  $token
+     * @param  string $token
      * @return \Illuminate\Contracts\Auth\Authenticatable|null
      */
     public function retrieveByToken($identifier, $token)
@@ -91,16 +99,21 @@ class LdapUserProvider implements UserProvider
      */
     public function retrieveByCredentials(array $credentials)
     {
-        // TODO: Implement retrieveByCredentials() method.
-
         // Get user via LDAP
+        $userDetails = $this->ldapService->getUserDetails($credentials['username']);
+        if ($userDetails === null) return null;
 
         // Search current user base by looking up a uid
+        $model = $this->createModel();
+        $currentUser = $model->newQuery()
+            ->where('external_auth_id', $userDetails['uid'])
+            ->first();
 
-        // If not exists create a new user instance with attached role
-        // but do not store it in the database yet
+        if ($currentUser !== null) return $currentUser;
 
-        //
+        $model->name = $userDetails['name'];
+        $model->external_auth_id = $userDetails['uid'];
+        return $model;
     }
 
     /**
@@ -112,6 +125,6 @@ class LdapUserProvider implements UserProvider
      */
     public function validateCredentials(Authenticatable $user, array $credentials)
     {
-        // TODO: Implement validateCredentials() method.
+        return $this->ldapService->validateUserCredentials($user, $credentials['username'], $credentials['password']);
     }
 }
index fecd7c88bf8754e0584e6500df69eac6b704b961..88918910ab1c2f25d96ffb87738912fe8bc4aa63 100644 (file)
@@ -3,6 +3,7 @@
 
 use BookStack\Role;
 use BookStack\User;
+use Setting;
 
 class UserRepo
 {
@@ -56,7 +57,7 @@ class UserRepo
      */
     public function attachDefaultRole($user)
     {
-        $roleId = \Setting::get('registration-role');
+        $roleId = Setting::get('registration-role');
         if ($roleId === false) $roleId = $this->role->getDefault()->id;
         $user->attachRoleId($roleId);
     }
index c698a1cf63a346082f4d9599e3ac286f91c49206..3d93bf7702b6300ee784949aeab989e949981858 100644 (file)
@@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model;
 class Role extends Model
 {
     /**
-     * Sets the default role name for newly registed users.
+     * Sets the default role name for newly registered users.
      * @var string
      */
     protected static $default = 'viewer';
index a540ab58bb1e26c165376b2c22673fc1b3fcdf30..bceed682a96e0657d7bb6225bab77c2c6a320760 100644 (file)
 
 
 use BookStack\Exceptions\LdapException;
+use Illuminate\Contracts\Auth\Authenticatable;
 
 class LdapService
 {
 
+    protected $ldapConnection;
+
+    /**
+     * Get the details of a user from LDAP using the given username.
+     * User found via configurable user filter.
+     * @param $userName
+     * @return array|null
+     * @throws LdapException
+     */
     public function getUserDetails($userName)
     {
+        $ldapConnection = $this->getConnection();
 
-        if(!function_exists('ldap_connect')) {
-            throw new LdapException('LDAP PHP extension not installed');
-        }
-
-
-        $ldapServer = explode(':', config('services.ldap.server'));
-        $ldapConnection = ldap_connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389);
-
-        if ($ldapConnection === false) {
-            throw new LdapException('Cannot connect to ldap server, Initial connection failed');
-        }
+        // Find user
+        $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]);
+        $baseDn = config('services.ldap.base_dn');
+        $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn']);
+        $users = ldap_get_entries($ldapConnection, $ldapSearch);
+        if ($users['count'] === 0) return null;
+
+        $user = $users[0];
+        return [
+            'uid'  => $user['uid'][0],
+            'name' => $user['cn'][0],
+            'dn'   => $user['dn']
+        ];
+    }
 
-        // Options
+    /**
+     * @param Authenticatable $user
+     * @param string          $username
+     * @param string          $password
+     * @return bool
+     * @throws LdapException
+     */
+    public function validateUserCredentials(Authenticatable $user, $username, $password)
+    {
+        $ldapUser = $this->getUserDetails($username);
+        if ($ldapUser === null) return false;
+        if ($ldapUser['uid'] !== $user->external_auth_id) return false;
 
-        ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // TODO - make configurable
+        $ldapConnection = $this->getConnection();
+        $ldapBind = @ldap_bind($ldapConnection, $ldapUser['dn'], $password);
+        return $ldapBind;
+    }
 
+    /**
+     * Bind the system user to the LDAP connection using the given credentials
+     * otherwise anonymous access is attempted.
+     * @param $connection
+     * @throws LdapException
+     */
+    protected function bindSystemUser($connection)
+    {
         $ldapDn = config('services.ldap.dn');
         $ldapPass = config('services.ldap.pass');
+
         $isAnonymous = ($ldapDn === false || $ldapPass === false);
         if ($isAnonymous) {
-            $ldapBind = ldap_bind($ldapConnection);
+            $ldapBind = ldap_bind($connection);
         } else {
-            $ldapBind = ldap_bind($ldapConnection, $ldapDn, $ldapPass);
+            $ldapBind = ldap_bind($connection, $ldapDn, $ldapPass);
         }
 
         if (!$ldapBind) throw new LdapException('LDAP access failed using ' . $isAnonymous ? ' anonymous bind.' : ' given dn & pass details');
+    }
 
-        // Find user
-        $userFilter = $this->buildFilter(config('services.ldap.user_filter'), ['user' => $userName]);
-        //dd($userFilter);
-        $baseDn = config('services.ldap.base_dn');
-        $ldapSearch = ldap_search($ldapConnection, $baseDn, $userFilter);
-        $users = ldap_get_entries($ldapConnection, $ldapSearch);
+    /**
+     * Get the connection to the LDAP server.
+     * Creates a new connection if one does not exist.
+     * @return resource
+     * @throws LdapException
+     */
+    protected function getConnection()
+    {
+        if ($this->ldapConnection !== null) return $this->ldapConnection;
 
-        dd($users);
-    }
+        // Check LDAP extension in installed
+        if (!function_exists('ldap_connect')) {
+            throw new LdapException('LDAP PHP extension not installed');
+        }
 
+        // Get port from server string if specified.
+        $ldapServer = explode(':', config('services.ldap.server'));
+        $ldapConnection = ldap_connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389);
+
+        if ($ldapConnection === false) {
+            throw new LdapException('Cannot connect to ldap server, Initial connection failed');
+        }
+
+        // Set any required options
+        ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); // TODO - make configurable
+
+        $this->ldapConnection = $ldapConnection;
+        return $this->ldapConnection;
+    }
 
-    private function buildFilter($filterString, $attrs)
+    /**
+     * Build a filter string by injecting common variables.
+     * @param       $filterString
+     * @param array $attrs
+     * @return string
+     */
+    protected function buildFilter($filterString, array $attrs)
     {
         $newAttrs = [];
         foreach ($attrs as $key => $attrText) {
-            $newKey = '${'.$key.'}';
+            $newKey = '${' . $key . '}';
             $newAttrs[$newKey] = $attrText;
         }
         return strtr($filterString, $newAttrs);
index 55d434cdf13c4f4df9b27e1b1e0b92a14fdf8ef7..0f2d5a69c8cb0a9c9787ac78b9978f11a9acf6cc 100644 (file)
@@ -70,7 +70,7 @@ return [
     'providers' => [
         'users' => [
             'driver' => env('AUTH_METHOD', 'eloquent'),
-            'model' => Bookstack\User::class,
+            'model' => BookStack\User::class,
         ],
 
         // 'users' => [
diff --git a/database/migrations/2016_01_11_210908_add_external_auth_to_users.php b/database/migrations/2016_01_11_210908_add_external_auth_to_users.php
new file mode 100644 (file)
index 0000000..dda8f3d
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddExternalAuthToUsers extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->string('external_auth_id')->index();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('external_auth_id');
+        });
+    }
+}
index aa910934580c7204401f8433ed4e4661e309918d..eb0a3182fdff440736676fba13cc4014158c74aa 100644 (file)
@@ -1,6 +1,6 @@
 <div class="form-group">
-    <label for="email">Username</label>
-    @include('form/text', ['name' => 'email', 'tabindex' => 1])
+    <label for="username">Username</label>
+    @include('form/text', ['name' => 'username', 'tabindex' => 1])
 </div>
 
 <div class="form-group">