namespace BookStack\Http\Controllers\Auth;
+use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
protected $redirectPath = '/';
protected $redirectAfterLogout = '/login';
+ protected $username = 'email';
+
protected $socialAuthService;
protected $emailConfirmationService;
$this->socialAuthService = $socialAuthService;
$this->emailConfirmationService = $emailConfirmationService;
$this->userRepo = $userRepo;
+ $this->username = config('auth.method') === 'standard' ? 'email' : 'username';
parent::__construct();
}
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
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]);
}
Route::get('/test', function() {
// TODO - remove this
$service = new \BookStack\Services\LdapService();
- $service->getUserDetails('ssmith');
+ dd($service->getUserDetails('ksmith'));
});
// Authenticated routes...
public function register()
{
Auth::provider('ldap', function($app, array $config) {
- return new LdapUserProvider($config['model']);
+ return new LdapUserProvider($config['model'], $app['BookStack\Services\LdapService']);
});
}
}
namespace BookStack\Providers;
+use BookStack\Role;
+use BookStack\Services\LdapService;
use BookStack\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\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;
}
/**
*/
public function createModel()
{
- $class = '\\'.ltrim($this->model, '\\');
-
+ $class = '\\' . ltrim($this->model, '\\');
return new $class;
}
* 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)
*/
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;
}
/**
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
- // TODO: Implement validateCredentials() method.
+ return $this->ldapService->validateUserCredentials($user, $credentials['username'], $credentials['password']);
}
}
use BookStack\Role;
use BookStack\User;
+use Setting;
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);
}
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';
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);
'providers' => [
'users' => [
'driver' => env('AUTH_METHOD', 'eloquent'),
- 'model' => Bookstack\User::class,
+ 'model' => BookStack\User::class,
],
// 'users' => [
--- /dev/null
+<?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');
+ });
+ }
+}
<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">