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;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cookie;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
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
|
* Show our themed login page
|
||||||
*/
|
*/
|
||||||
public function showLoginForm()
|
public function showLoginForm()
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
namespace App\Http;
|
namespace App\Http;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
|
|
||||||
|
|
||||||
use App\Http\Middleware\GuestUser;
|
use App\Http\Middleware\GuestUser;
|
||||||
|
use App\Http\Middleware\SwapinAuthUser;
|
||||||
|
|
||||||
class Kernel extends HttpKernel
|
class Kernel extends HttpKernel
|
||||||
{
|
{
|
||||||
@ -36,7 +36,8 @@ class Kernel extends HttpKernel
|
|||||||
\App\Http\Middleware\EncryptCookies::class,
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
SwapinAuthUser::class,
|
||||||
|
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
@ -46,6 +47,8 @@ class Kernel extends HttpKernel
|
|||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
'throttle:60,1',
|
'throttle:60,1',
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
SwapinAuthUser::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::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 Illuminate\Support\ServiceProvider;
|
||||||
use LdapRecord\Configuration\DomainConfiguration;
|
use LdapRecord\Configuration\DomainConfiguration;
|
||||||
|
use LdapRecord\Laravel\LdapRecord;
|
||||||
|
|
||||||
|
use App\Ldap\LdapUserRepository;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Register any application services.
|
* Register any application services.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
// Add a new option available to be set in the configuration:
|
// Add a new option available to be set in the configuration:
|
||||||
DomainConfiguration::extend('name', $default = null);
|
DomainConfiguration::extend('name', $default = null);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Use our LdapUserRepository to support multiple baseDN querying
|
||||||
* Bootstrap any application services.
|
LdapRecord::locateUsersUsing(LdapUserRepository::class);
|
||||||
*
|
}
|
||||||
* @return void
|
|
||||||
*/
|
/**
|
||||||
public function boot()
|
* Bootstrap any application services.
|
||||||
{
|
*
|
||||||
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
* @return void
|
||||||
}
|
*/
|
||||||
}
|
public function boot()
|
||||||
|
{
|
||||||
|
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
||||||
|
}
|
||||||
|
}
|
@ -24,9 +24,9 @@
|
|||||||
<td>BaseDN(s)</td>
|
<td>BaseDN(s)</td>
|
||||||
<td>
|
<td>
|
||||||
<table class="table table-sm table-borderless">
|
<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>
|
<tr>
|
||||||
<td>{{ $item->getDn() }}</td>
|
<td class="pl-0">{{ $item->getDn() }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</table>
|
||||||
|
@ -13,11 +13,11 @@ class GetBaseDNTest extends TestCase
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
* @throws \LdapRecord\Query\ObjectNotFoundException
|
* @throws \LdapRecord\Query\ObjectNotFoundException
|
||||||
* @covers \App\Ldap\Entry::baseDN()
|
* @covers \App\Ldap\Entry::baseDNs()
|
||||||
*/
|
*/
|
||||||
public function testBaseDnExists()
|
public function testBaseDnExists()
|
||||||
{
|
{
|
||||||
$o = (new Entry)->baseDN();
|
$o = (new Entry)->baseDNs();
|
||||||
|
|
||||||
$this->assertIsObject($o);
|
$this->assertIsObject($o);
|
||||||
$this->assertCount(6,$o->toArray());
|
$this->assertCount(6,$o->toArray());
|
||||||
|
Loading…
Reference in New Issue
Block a user