From 6751c9dd818c9ce910a5b72056b8dd9b9260036d Mon Sep 17 00:00:00 2001 From: Deon George Date: Mon, 30 Jan 2023 21:37:33 +1100 Subject: [PATCH] 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. --- app/Http/Controllers/Auth/LoginController.php | 31 ++++++++ app/Http/Kernel.php | 7 +- app/Http/Middleware/SwapinAuthUser.php | 50 +++++++++++++ app/Ldap/Connection.php | 20 +++++ app/Ldap/Guard.php | 29 ++++++++ app/Ldap/LdapUserRepository.php | 73 +++++++++++++++++++ app/Providers/AppServiceProvider.php | 42 ++++++----- resources/views/debug.blade.php | 4 +- tests/Unit/GetBaseDNTest.php | 4 +- 9 files changed, 236 insertions(+), 24 deletions(-) create mode 100644 app/Http/Middleware/SwapinAuthUser.php create mode 100644 app/Ldap/Connection.php create mode 100644 app/Ldap/Guard.php create mode 100644 app/Ldap/LdapUserRepository.php diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3f0ae8f..90a4c4f 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -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() diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 018f1b9..5bc2312 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -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, ], ]; diff --git a/app/Http/Middleware/SwapinAuthUser.php b/app/Http/Middleware/SwapinAuthUser.php new file mode 100644 index 0000000..f45081a --- /dev/null +++ b/app/Http/Middleware/SwapinAuthUser.php @@ -0,0 +1,50 @@ +addConnection(new Connection(config('ldap.connections.'.$key)),$key); + + return $next($request); + } +} \ No newline at end of file diff --git a/app/Ldap/Connection.php b/app/Ldap/Connection.php new file mode 100644 index 0000000..b749c81 --- /dev/null +++ b/app/Ldap/Connection.php @@ -0,0 +1,20 @@ +authGuardResolver = function () { + return new Guard($this->ldap, $this->configuration); + }; + } +} \ No newline at end of file diff --git a/app/Ldap/Guard.php b/app/Ldap/Guard.php new file mode 100644 index 0000000..728e632 --- /dev/null +++ b/app/Ldap/Guard.php @@ -0,0 +1,29 @@ +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; + } +} \ No newline at end of file diff --git a/app/Ldap/LdapUserRepository.php b/app/Ldap/LdapUserRepository.php new file mode 100644 index 0000000..990a00a --- /dev/null +++ b/app/Ldap/LdapUserRepository.php @@ -0,0 +1,73 @@ +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; + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 019fc79..b5540ce 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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'); + } +} \ No newline at end of file diff --git a/resources/views/debug.blade.php b/resources/views/debug.blade.php index e3ac732..119a33b 100644 --- a/resources/views/debug.blade.php +++ b/resources/views/debug.blade.php @@ -24,9 +24,9 @@ BaseDN(s) - @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) - + @endforeach
{{ $item->getDn() }}{{ $item->getDn() }}
diff --git a/tests/Unit/GetBaseDNTest.php b/tests/Unit/GetBaseDNTest.php index b539d68..d7ceffd 100644 --- a/tests/Unit/GetBaseDNTest.php +++ b/tests/Unit/GetBaseDNTest.php @@ -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());