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:
parent
413f1ec065
commit
6751c9dd81
@ -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()
|
||||
|
@ -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,
|
||||
],
|
||||
];
|
||||
|
50
app/Http/Middleware/SwapinAuthUser.php
Normal file
50
app/Http/Middleware/SwapinAuthUser.php
Normal 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
20
app/Ldap/Connection.php
Normal 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
29
app/Ldap/Guard.php
Normal 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;
|
||||
}
|
||||
}
|
73
app/Ldap/LdapUserRepository.php
Normal file
73
app/Ldap/LdapUserRepository.php
Normal 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;
|
||||
}
|
||||
}
|
@ -4,27 +4,33 @@ namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use LdapRecord\Configuration\DomainConfiguration;
|
||||
use LdapRecord\Laravel\LdapRecord;
|
||||
|
||||
use App\Ldap\LdapUserRepository;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// Add a new option available to be set in the configuration:
|
||||
DomainConfiguration::extend('name', $default = null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
||||
}
|
||||
}
|
||||
// Use our LdapUserRepository to support multiple baseDN querying
|
||||
LdapRecord::locateUsersUsing(LdapUserRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user