Enable authentication if the LDAP server has multiple base DNs. Store the user's credentials in a cookie/session, and swap them out to the configured credentials when logged in.

This commit is contained in:
Deon George 2023-01-30 21:37:33 +11:00
parent 413f1ec065
commit 6751c9dd81
9 changed files with 236 additions and 24 deletions

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
@ -49,6 +51,35 @@ class LoginController extends Controller
}
/**
* We need to delete our encrypted username/password cookies
*
* @note The rest of this function is the same as a normal laravel logout as in AuthenticatesUsers::class
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed
*/
public function logout(Request $request)
{
// Delete our LDAP authentication cookies
Cookie::queue(Cookie::forget('username_encrypt'));
Cookie::queue(Cookie::forget('password_encrypt'));
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) {
return $response;
}
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect('/');
}
/**
*
* Show our themed login page
*/
public function showLoginForm()

View File

@ -3,9 +3,9 @@
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
use App\Http\Middleware\GuestUser;
use App\Http\Middleware\SwapinAuthUser;
class Kernel extends HttpKernel
{
@ -36,7 +36,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
SwapinAuthUser::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
@ -46,6 +47,8 @@ class Kernel extends HttpKernel
'api' => [
'throttle:60,1',
\App\Http\Middleware\EncryptCookies::class,
SwapinAuthUser::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
// use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
// use Illuminate\Support\Facades\Session;
use LdapRecord\Container;
use App\Ldap\Connection;
class SwapinAuthUser
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$key = config('ldap.default');
/*
// Rebuild our connection with the authenticated user.
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Crypt::decryptString(Session::get('username_encrypt')));
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
} else
*/
if (Cookie::has('username_encrypt') && Cookie::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Cookie::get('username_encrypt'));
Config::set('ldap.connections.'.$key.'.password',Cookie::get('password_encrypt'));
Log::debug('Swapping out configured LDAP credentials with the user\'s cookie.');
}
// We need to override our Connection object so that we can store and retrieve the logged in user and swap out the credentials to use them.
Container::getInstance()->addConnection(new Connection(config('ldap.connections.'.$key)),$key);
return $next($request);
}
}

20
app/Ldap/Connection.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace App\Ldap;
use LdapRecord\Connection as ConnectionBase;
use LdapRecord\LdapInterface;
class Connection extends ConnectionBase
{
public function __construct($config = [], LdapInterface $ldap = null)
{
parent::__construct($config,$ldap);
// We need to override this so that we use our own Guard, that stores the users credentials in the session
$this->authGuardResolver = function () {
return new Guard($this->ldap, $this->configuration);
};
}
}

29
app/Ldap/Guard.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Ldap;
use Illuminate\Support\Facades\Cookie;
// use Illuminate\Support\Facades\Crypt;
use LdapRecord\Auth\Guard as GuardBase;
class Guard extends GuardBase
{
public function attempt($username, $password, $stayBound = false)
{
if ($result = parent::attempt($username,$password,$stayBound)) {
/*
* We can either use our session or cookies to store this. If using session, then Http/Kernel needs to be
* updated to start a session for API calls.
// We need to store our password so that we can swap in the user in during SwapinAuthUser::class middleware
request()->session()->put('username_encrypt',Crypt::encryptString($username));
request()->session()->put('password_encrypt',Crypt::encryptString($password));
*/
// For our API calls, we store the cookie - which our cookies are already encrypted
Cookie::queue('username_encrypt',$username);
Cookie::queue('password_encrypt',$password);
}
return $result;
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Ldap;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use LdapRecord\Laravel\Events\Auth\DiscoveredWithCredentials;
use LdapRecord\Laravel\LdapUserRepository as LdapUserRepositoryBase;
use LdapRecord\Models\Model;
class LdapUserRepository extends LdapUserRepositoryBase
{
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
*
* @return Model|null
* @throws \LdapRecord\Query\ObjectNotFoundException
*/
public function findByCredentials(array $credentials = []): ?Model
{
if (empty($credentials)) {
return NULL;
}
// Look for a user using all our baseDNs
foreach ((new Entry)->baseDNs() as $base) {
$query = $this->query()->setBaseDn($base);
foreach ($credentials as $key => $value) {
if (Str::contains($key, $this->bypassCredentialKeys)) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
if (! is_null($user = $query->first())) {
event(new DiscoveredWithCredentials($user));
return $user;
}
}
return NULL;
}
/**
* Get a user by their object GUID.
*
* @param string $guid
*
* @return Model|null
* @throws \LdapRecord\Query\ObjectNotFoundException
*/
public function findByGuid($guid): ?Model
{
// Look for a user using all our baseDNs
foreach ((new Entry)->baseDNs() as $base) {
$user = $this->query()->setBaseDn($base)->findByGuid($guid);
if ($user)
return $user;
}
return NULL;
}
}

View File

@ -4,6 +4,9 @@ namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Laravel\LdapRecord;
use App\Ldap\LdapUserRepository;
class AppServiceProvider extends ServiceProvider
{
@ -16,6 +19,9 @@ class AppServiceProvider extends ServiceProvider
{
// Add a new option available to be set in the configuration:
DomainConfiguration::extend('name', $default = null);
// Use our LdapUserRepository to support multiple baseDN querying
LdapRecord::locateUsersUsing(LdapUserRepository::class);
}
/**

View File

@ -24,9 +24,9 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach(\App\Ldap\Entry::baseDN()->sort(function($item) { return $item->sortKey; }) as $item)
@foreach(\App\Ldap\Entry::baseDNs()->sort(function($item) { return $item->sortKey; }) as $item)
<tr>
<td>{{ $item->getDn() }}</td>
<td class="pl-0">{{ $item->getDn() }}</td>
</tr>
@endforeach
</table>

View File

@ -13,11 +13,11 @@ class GetBaseDNTest extends TestCase
*
* @return void
* @throws \LdapRecord\Query\ObjectNotFoundException
* @covers \App\Ldap\Entry::baseDN()
* @covers \App\Ldap\Entry::baseDNs()
*/
public function testBaseDnExists()
{
$o = (new Entry)->baseDN();
$o = (new Entry)->baseDNs();
$this->assertIsObject($o);
$this->assertCount(6,$o->toArray());