Compare commits

...

6 Commits

Author SHA1 Message Date
aa726db11a Change we now store logged in user details in session, instead of cookies.
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m28s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
This is so when the session expires, the logged in user details are expired as well, which wasnt happening with cookies.
2025-04-26 18:48:30 +10:00
b6dbaed606 Move our /api routes into /ajax under web.php. The /api routes werent authenticated and may not have been using the logged in users details 2025-04-26 18:02:50 +10:00
5f8eb2bb91 Move out view variables until after our session has been setup. This was needed so that auth()->user() could be resolved correctly and needed to be done after we have started the session and swapped in the users cookies 2025-04-26 18:02:50 +10:00
e0fdc49b41 Dynamically work out objectclasses on the current entry, this fixes usage issues between adding objectclasses and adding attribute that are now available from new objectclasses, as well as determining that they are not dynamic
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m26s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-25 17:21:12 +10:00
986d33a22e Fix when rendering changes to 2 or more attributes, the update confirmation table had one too many rowspan values for the Attribute.
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 4m5s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
Fix updating an entry by adding an new objectclass
2025-04-24 17:17:58 +10:00
c3f82d992b Revert version to 2.1.2-dev
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m20s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-21 18:08:48 +10:00
20 changed files with 119 additions and 132 deletions

View File

@ -8,9 +8,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\LdapRecordException; use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model; use LdapRecord\Models\Model;
use LdapRecord\Query\Collection as LDAPCollection; use LdapRecord\Query\Collection as LDAPCollection;
@ -173,16 +171,6 @@ final class Server
} catch (LdapRecordException $e) { } catch (LdapRecordException $e) {
switch ($e->getDetailedError()?->getErrorCode()) { switch ($e->getDetailedError()?->getErrorCode()) {
case 49: case 49:
// Since we failed authentication, we should delete our auth cookie
if (Cookie::has('password_encrypt')) {
Log::alert('Clearing user credentials and logging out');
Cookie::queue(Cookie::forget('password_encrypt'));
Cookie::queue(Cookie::forget('username_encrypt'));
Session::invalidate();
}
abort(401,$e->getDetailedError()->getErrorMessage()); abort(401,$e->getDetailedError()->getErrorMessage());
default: default:
@ -196,8 +184,8 @@ final class Server
/** /**
* @note While we are caching our baseDNs, it seems if we have more than 1, * @note While we are caching our baseDNs, it seems if we have more than 1,
* our caching doesnt generate a hit on a subsequent call to this function (before the cache expires). * our caching doesnt generate a hit on a subsequent call to this function (before the cache expires).
* IE: If we have 5 baseDNs, it takes 5 calls to this function to case them all. * IE: If we have 5 baseDNs, it takes 5 calls to this function to cache them all.
* @todo Possibly a bug wtih ldaprecord, so need to investigate * @todo Possibly a bug with ldaprecord, so need to investigate
*/ */
$result = collect(); $result = collect();
foreach ($base->namingcontexts as $dn) foreach ($base->namingcontexts as $dn)

View File

@ -10,7 +10,7 @@ use Illuminate\Support\Collection;
use App\Classes\LDAP\Server; use App\Classes\LDAP\Server;
class APIController extends Controller class AjaxController extends Controller
{ {
/** /**
* Get the LDAP server BASE DNs * Get the LDAP server BASE DNs

View File

@ -5,41 +5,43 @@ namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
class LoginController extends Controller class LoginController extends Controller
{ {
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Login Controller | Login Controller
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This controller handles authenticating users for the application and | This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait | redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications. | to conveniently provide its functionality to your applications.
| |
*/ */
use AuthenticatesUsers; use AuthenticatesUsers;
/** /**
* Where to redirect users after login. * Where to redirect users after login.
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected $redirectTo = '/';
/** /**
* Create a new controller instance. * Create a new controller instance.
* *
* @return void * @return void
*/ */
public function __construct() public function __construct()
{ {
$this->middleware('guest')->except('logout'); $this->middleware('guest')
} ->except('logout');
}
protected function credentials(Request $request): array protected function credentials(Request $request): array
{ {
@ -58,17 +60,14 @@ class LoginController extends Controller
*/ */
public function logout(Request $request) public function logout(Request $request)
{ {
// Delete our LDAP authentication cookies $user = Auth::user();
Cookie::queue(Cookie::forget('username_encrypt'));
Cookie::queue(Cookie::forget('password_encrypt'));
$this->guard()->logout(); $this->guard()->logout();
$request->session()->invalidate(); $request->session()->invalidate();
$request->session()->regenerateToken(); $request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) { if ($response = $this->loggedOut($request)) {
Log::info(sprintf('Logged out [%s]',$user->dn));
return $response; return $response;
} }
@ -100,4 +99,4 @@ class LoginController extends Controller
{ {
return login_attr_name(); return login_attr_name();
} }
} }

View File

@ -4,7 +4,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Session;
class AllowAnonymous class AllowAnonymous
{ {
@ -17,7 +17,9 @@ class AllowAnonymous
*/ */
public function handle(Request $request,Closure $next): mixed public function handle(Request $request,Closure $next): mixed
{ {
if (((! Cookie::has('username_encrypt')) || (! Cookie::has('password_encrypt'))) && (! config('pla.allow_guest',FALSE))) if ((! config('pla.allow_guest',FALSE))
&& ($request->path() !== 'login')
&& ((! Session::has('username_encrypt')) || (! Session::has('password_encrypt'))))
return redirect() return redirect()
->to('/login'); ->to('/login');

View File

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use App\Classes\LDAP\Server; use App\Classes\LDAP\Server;
use App\Ldap\User;
/** /**
* This sets up our application session with any required values, ultimately for cache optimisation reasons * This sets up our application session with any required values, ultimately for cache optimisation reasons
@ -25,9 +24,6 @@ class ApplicationSession
{ {
Config::set('server',new Server); Config::set('server',new Server);
view()->share('server', Config::get('server'));
view()->share('user', auth()->user() ?: new User);
return $next($request); return $next($request);
} }
} }

View File

@ -5,8 +5,9 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\Container; use LdapRecord\Container;
use App\Ldap\Connection; use App\Ldap\Connection;
@ -28,21 +29,11 @@ class SwapinAuthUser
if (! array_key_exists($key,config('ldap.connections'))) if (! array_key_exists($key,config('ldap.connections')))
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key)); abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
/*
// Rebuild our connection with the authenticated user.
if (Session::has('username_encrypt') && Session::has('password_encrypt')) { 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.'.username',Crypt::decryptString(Session::get('username_encrypt')));
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt'))); Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
} else Log::debug('Swapping out configured LDAP credentials with the user\'s session.',['key'=>$key]);
*/
// @todo it seems sometimes we have cookies that show the logged in user, but Auth::user() has expired?
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.',['key'=>$key,'user'=>Cookie::get('username_encrypt')]);
} }
// 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. // 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.

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use App\Ldap\User;
/**
* This sets up our application session with any required values, ultimately for cache optimisation reasons
*/
class ViewVariables
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request,Closure $next): mixed
{
view()->share('server',Config::get('server'));
view()->share('user',auth()->user() ?: new User);
return $next($request);
}
}

View File

@ -2,26 +2,20 @@
namespace App\Ldap; namespace App\Ldap;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Crypt;
// use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Log;
use LdapRecord\Auth\Guard as GuardBase; use LdapRecord\Auth\Guard as GuardBase;
class Guard extends GuardBase class Guard extends GuardBase
{ {
public function attempt(string $username, string $password, bool $stayBound = false): bool public function attempt(string $username, string $password, bool $stayBound = false): bool
{ {
if ($result = parent::attempt($username,$password,$stayBound)) { Log::info(sprintf('Attempting login for [%s] with password [%s]',$username,($password ? str_repeat('*',16) : str_repeat('?',16))));
/*
* 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 if ($result = parent::attempt($username,$password,$stayBound)) {
Cookie::queue('username_encrypt',$username); // Store user details so we can swap in auth details in SwapinAuthUser
Cookie::queue('password_encrypt',$password); session()->put('username_encrypt',Crypt::encryptString($username));
session()->put('password_encrypt',Crypt::encryptString($password));
} }
return $result; return $result;

View File

@ -1,32 +1,27 @@
<?php <?php
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser}; use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser,ViewVariables};
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('web', [ $middleware->appendToGroup(
SwapinAuthUser::class, group: 'web',
ApplicationSession::class, middleware: [
CheckUpdate::class, AllowAnonymous::class,
]); ApplicationSession::class,
SwapinAuthUser::class,
$middleware->prependToGroup('api', [ ViewVariables::class,
EncryptCookies::class, CheckUpdate::class,
SwapinAuthUser::class, ]);
ApplicationSession::class,
AllowAnonymous::class,
]);
$middleware->trustProxies(at: [ $middleware->trustProxies(at: [
'10.0.0.0/8', '10.0.0.0/8',

View File

@ -1,5 +1,5 @@
<?php <?php
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
]; ];

View File

@ -68,7 +68,7 @@ return [
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'), 'level' => env('LOG_LEVEL', 'info'),
'days' => env('LOG_DAILY_DAYS', 14), 'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true, 'replace_placeholders' => true,
], ],

View File

@ -1 +1 @@
v2.1.1-rel v2.1.2-dev

4
public/js/custom.js vendored
View File

@ -59,7 +59,7 @@ $(document).ready(function() {
if (typeof basedn !== 'undefined') { if (typeof basedn !== 'undefined') {
sources = basedn; sources = basedn;
} else { } else {
sources = { url: 'api/bases' }; sources = { url: 'ajax/bases' };
} }
// Attach the fancytree widget to an existing <div id="tree"> element // Attach the fancytree widget to an existing <div id="tree"> element
@ -95,7 +95,7 @@ $(document).ready(function() {
source: sources, source: sources,
lazyLoad: function(event,data) { lazyLoad: function(event,data) {
data.result = { data.result = {
url: '/api/children', url: '/ajax/children',
data: {key: data.node.data.item,depth: 1} data: {key: data.node.data.item,depth: 1}
}; };

View File

@ -54,6 +54,9 @@
var rendered = false; var rendered = false;
var newadded = []; var newadded = [];
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
if (newadded.length) if (newadded.length)
process_oc(); process_oc();
@ -88,7 +91,7 @@
// Get a list of attributes already on the page, so we dont double up // Get a list of attributes already on the page, so we dont double up
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item, url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false, cache: false,
success: function(data) { success: function(data) {
// Render any must attributes // Render any must attributes
@ -153,7 +156,7 @@
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item, url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false, cache: false,
success: function(data) { success: function(data) {
var attrs = []; var attrs = [];

View File

@ -103,9 +103,10 @@
$(document).ready(function() { $(document).ready(function() {
@if($step === 2) @if($step === 2)
var oc = {!! $o->getObject('objectclass')->values !!};
$('#newattr').on('change',function(item) { $('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '{{ url('entry/attr/add') }}/'+item.target.value, url: '{{ url('entry/attr/add') }}/'+item.target.value,

View File

@ -174,7 +174,6 @@
@section('page-scripts') @section('page-scripts')
<script type="text/javascript"> <script type="text/javascript">
var dn = '{{ $o->getDNSecure() }}'; var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function editmode() { function editmode() {
$('#dn-edit input[name="dn"]').val(dn); $('#dn-edit input[name="dn"]').val(dn);
@ -225,6 +224,9 @@
}); });
$('#newattr').on('change',function(item) { $('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
beforeSend: function() {}, beforeSend: function() {},

View File

@ -58,7 +58,7 @@
return false; return false;
$.ajax({ $.ajax({
url: '{{ url('api/schema/view') }}', url: '{{ url('ajax/schema/view') }}',
method: 'POST', method: 'POST',
data: { type: type }, data: { type: type },
dataType: 'html', dataType: 'html',

View File

@ -37,7 +37,7 @@
<tbody> <tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo) @foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr> <tr>
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}"> <th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr> <abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th> </th>
@ -54,7 +54,7 @@
<td colspan="2" class="text-center">@lang('Ignoring blank value')</td> <td colspan="2" class="text-center">@lang('Ignoring blank value')</td>
@else @else
<td>{{ (($r=$oo->render_item_old($dotkey)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td> <td>{{ (($r=$oo->render_item_old($dotkey)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo,$dotkey) }}"></td> <td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ $oo->no_attr_tags ? \App\Ldap\Entry::TAG_NOTAG : collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo->values->dot(),$dotkey) }}"></td>
@endif @endif
@endforeach @endforeach
</tr> </tr>

View File

@ -1,23 +0,0 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\APIController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::controller(APIController::class)->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});

View File

@ -2,7 +2,7 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController; use App\Http\Controllers\{AjaxController,HomeController};
use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Auth\LoginController;
use App\Http\Middleware\AllowAnonymous; use App\Http\Middleware\AllowAnonymous;
@ -57,4 +57,13 @@ Route::controller(HomeController::class)->group(function() {
Route::view('modal/export/{dn}','modals.entry-export'); Route::view('modal/export/{dn}','modals.entry-export');
Route::view('modal/userpassword-check/{dn}','modals.entry-userpassword-check'); Route::view('modal/userpassword-check/{dn}','modals.entry-userpassword-check');
}); });
}); });
Route::controller(AjaxController::class)
->prefix('ajax')
->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});