From 996d7bb1dcaab2ff5bf40dd6247e4fdbb88341ac Mon Sep 17 00:00:00 2001 From: Deon George Date: Sun, 23 Feb 2025 18:14:41 +1100 Subject: [PATCH] This commit is mainly as a result of creating DN entries and improves some backend functions: * Enable creation of new entries, * Change all our ajax frames to go through /frames URI instead of /dn, * Add our frame command to the encrypted DN, * Automatically redirect to root URL when selecting a tree item and currently in another path (as a result of a prior POST activity), * Some validation improvements DNExists/HasStructuralObjectClass --- app/Classes/LDAP/Attribute.php | 4 +- app/Classes/LDAP/Attribute/RDN.php | 49 +++++ app/Classes/LDAP/Schema/ObjectClass.php | 13 +- .../LDAP/Schema/ObjectClassAttribute.php | 11 +- app/Http/Controllers/APIController.php | 48 +++-- app/Http/Controllers/HomeController.php | 188 +++++++++++++++--- app/Http/Requests/EntryAddRequest.php | 71 +++++++ app/Http/Requests/EntryRequest.php | 14 +- app/Http/Requests/ImportRequest.php | 8 +- app/Ldap/Entry.php | 36 +++- app/Rules/DNExists.php | 27 +++ app/Rules/HasStructuralObjectClass.php | 23 +++ public/css/custom.css | 7 +- public/css/fixes.css | 4 + public/js/custom.js | 27 ++- .../views/components/attribute/rdn.blade.php | 64 ++++++ .../attribute/widget/options.blade.php | 9 +- .../views/components/form/base.blade.php | 2 +- .../views/components/form/select.blade.php | 16 +- .../views/fragment/dn/add_attr.blade.php | 22 ++ resources/views/fragment/dn/header.blade.php | 2 +- resources/views/frames/create.blade.php | 123 ++++++++++++ resources/views/frames/dn.blade.php | 30 +-- resources/views/frames/import.blade.php | 2 +- resources/views/frames/schema.blade.php | 3 +- resources/views/home.blade.php | 25 ++- routes/web.php | 6 +- 27 files changed, 687 insertions(+), 147 deletions(-) create mode 100644 app/Classes/LDAP/Attribute/RDN.php create mode 100644 app/Http/Requests/EntryAddRequest.php create mode 100644 app/Rules/DNExists.php create mode 100644 app/Rules/HasStructuralObjectClass.php create mode 100644 resources/views/components/attribute/rdn.blade.php create mode 100644 resources/views/fragment/dn/add_attr.blade.php create mode 100644 resources/views/frames/create.blade.php diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index 0d4b2413..4ae4c195 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -145,9 +145,9 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator // Attribute values 'values' => $this->values, // Required by Object Classes - 'required_by' => $this->schema->required_by_object_classes, + 'required_by' => $this->schema?->required_by_object_classes ?: collect(), // Used in Object Classes - 'used_in' => $this->schema->used_in_object_classes, + 'used_in' => $this->schema?->used_in_object_classes ?: collect(), default => throw new \Exception('Unknown key:' . $key), }; diff --git a/app/Classes/LDAP/Attribute/RDN.php b/app/Classes/LDAP/Attribute/RDN.php new file mode 100644 index 00000000..48c99575 --- /dev/null +++ b/app/Classes/LDAP/Attribute/RDN.php @@ -0,0 +1,49 @@ + $this->base, + 'attrs' => $this->attrs->pluck('name'), + default => parent::__get($key), + }; + } + + public function hints(): array + { + return [ + 'required' => __('RDN is required') + ]; + } + + public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View + { + return view('components.attribute.rdn') + ->with('o',$this); + } + + public function setAttributes(Collection $attrs): void + { + $this->attrs = $attrs; + } + + public function setBase(string $base): void + { + $this->base = $base; + } +} \ No newline at end of file diff --git a/app/Classes/LDAP/Schema/ObjectClass.php b/app/Classes/LDAP/Schema/ObjectClass.php index 26dcddc5..50afa518 100644 --- a/app/Classes/LDAP/Schema/ObjectClass.php +++ b/app/Classes/LDAP/Schema/ObjectClass.php @@ -208,7 +208,7 @@ final class ObjectClass extends Base public function __get(string $key): mixed { return match ($key) { - 'attributes' => $this->getAllAttrs(), + 'attributes' => $this->getAllAttrs(TRUE), 'sup' => $this->sup_classes, 'type_name' => match ($this->type) { Server::OC_STRUCTURAL => 'Structural', @@ -223,13 +223,18 @@ final class ObjectClass extends Base /** * Return a list of attributes that this objectClass provides * + * @param bool $parents * @return Collection * @throws InvalidUsage */ - public function getAllAttrs(): Collection + public function getAllAttrs(bool $parents=FALSE): Collection { - return $this->getMustAttrs() - ->merge($this->getMayAttrs()); + return $this->getMustAttrs($parents) + ->transform(function($item) { + $item->required = true; + return $item; + }) + ->merge($this->getMayAttrs($parents)); } /** diff --git a/app/Classes/LDAP/Schema/ObjectClassAttribute.php b/app/Classes/LDAP/Schema/ObjectClassAttribute.php index ed8eb309..79cd475b 100644 --- a/app/Classes/LDAP/Schema/ObjectClassAttribute.php +++ b/app/Classes/LDAP/Schema/ObjectClassAttribute.php @@ -16,6 +16,7 @@ namespace App\Classes\LDAP\Schema; final class ObjectClassAttribute extends Base { // This Attribute's root. private string $source; + public bool $required = FALSE; /** * Creates a new ObjectClassAttribute with specified name and source objectClass. @@ -31,11 +32,9 @@ final class ObjectClassAttribute extends Base { public function __get(string $key): mixed { - switch ($key) { - case 'source': - return $this->source; - - default: return parent::__get($key); - } + return match ($key) { + 'source' => $this->source, + default => parent::__get($key), + }; } } \ No newline at end of file diff --git a/app/Http/Controllers/APIController.php b/app/Http/Controllers/APIController.php index f2283029..691aa34c 100644 --- a/app/Http/Controllers/APIController.php +++ b/app/Http/Controllers/APIController.php @@ -16,21 +16,21 @@ class APIController extends Controller * Get the LDAP server BASE DNs * * @return Collection - * @throws LdapRecord\Query\ObjectNotFoundException + * @throws \LdapRecord\Query\ObjectNotFoundException */ public function bases(): Collection { $base = Server::baseDNs() ?: collect(); - return $base->transform(function($item) { - return [ - 'title'=>$item->getRdn(), - 'item'=>$item->getDNSecure(), - 'lazy'=>TRUE, - 'icon'=>'fa-fw fas fa-sitemap', - 'tooltip'=>$item->getDn(), - ]; - }); + return $base + ->transform(fn($item)=> + [ + 'title'=>$item->getRdn(), + 'item'=>$item->getDNSecure(), + 'lazy'=>TRUE, + 'icon'=>'fa-fw fas fa-sitemap', + 'tooltip'=>$item->getDn(), + ]); } /** @@ -41,19 +41,31 @@ class APIController extends Controller { $levels = $request->query('depth',1); $dn = Crypt::decryptString($request->query('key')); + + // Sometimes our key has a command, so we'll ignore it + if (str_starts_with($dn,'*') && ($x=strpos($dn,'|'))) + $dn = substr($dn,$x+1); + Log::debug(sprintf('%s: Query [%s] - Levels [%d]',__METHOD__,$dn,$levels)); return (config('server')) ->children($dn) - ->transform(function($item) { - return [ + ->transform(fn($item)=> + [ 'title'=>$item->getRdn(), 'item'=>$item->getDNSecure(), 'icon'=>$item->icon(), 'lazy'=>Arr::get($item->getAttribute('hassubordinates'),0) == 'TRUE', 'tooltip'=>$item->getDn(), - ]; - }); + ]) + ->prepend( + [ + 'title'=>sprintf('[%s]',__('Create Entry')), + 'item'=>Crypt::encryptString(sprintf('*%s|%s','create',$dn)), + 'lazy'=>FALSE, + 'icon'=>'fas fa-fw fa-square-plus text-warning', + 'tooltip'=>__('Create new LDAP item here'), + ]); } public function schema_view(Request $request) @@ -63,20 +75,20 @@ class APIController extends Controller switch($request->type) { case 'objectclasses': return view('fragment.schema.objectclasses') - ->with('objectclasses',$server->schema('objectclasses')->sortBy(function($item) { return strtolower($item->name); })); + ->with('objectclasses',$server->schema('objectclasses')->sortBy(fn($item)=>strtolower($item->name))); case 'attributetypes': return view('fragment.schema.attributetypes') ->with('server',$server) - ->with('attributetypes',$server->schema('attributetypes')->sortBy(function($item) { return strtolower($item->name); })); + ->with('attributetypes',$server->schema('attributetypes')->sortBy(fn($item)=>strtolower($item->name))); case 'ldapsyntaxes': return view('fragment.schema.ldapsyntaxes') - ->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(function($item) { return strtolower($item->description); })); + ->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(fn($item)=>strtolower($item->description))); case 'matchingrules': return view('fragment.schema.matchingrules') - ->with('matchingrules',$server->schema('matchingrules')->sortBy(function($item) { return strtolower($item->name); })); + ->with('matchingrules',$server->schema('matchingrules')->sortBy(fn($item)=>strtolower($item->name))); default: abort(404); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 74cb4a34..a4123dc8 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use Illuminate\Contracts\View\View; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -12,20 +13,21 @@ use Illuminate\Support\Facades\Redirect; use LdapRecord\Exceptions\InsufficientAccessException; use LdapRecord\LdapRecordException; use LdapRecord\Query\ObjectNotFoundException; +use Nette\NotImplementedException; +use App\Classes\LDAP\Attribute\Factory; use App\Classes\LDAP\{Attribute,Server}; use App\Classes\LDAP\Import\LDIF as LDIFImport; use App\Classes\LDAP\Export\LDIF as LDIFExport; use App\Exceptions\Import\{GeneralException,VersionException}; use App\Exceptions\InvalidUsage; -use App\Http\Requests\{EntryRequest,ImportRequest}; +use App\Http\Requests\{EntryRequest,EntryAddRequest,ImportRequest}; use App\Ldap\Entry; use App\View\Components\AttributeType; -use Nette\NotImplementedException; class HomeController extends Controller { - private function bases() + private function bases(): Collection { $base = Server::baseDNs() ?: collect(); @@ -51,21 +53,38 @@ class HomeController extends Controller } /** - * Render a specific DN + * Create a new object in the LDAP server * - * @param Request $request + * @param EntryAddRequest $request * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + * @throws InvalidUsage */ - public function dn_frame(Request $request) + public function entry_add(EntryAddRequest $request) { - $dn = Crypt::decryptString($request->post('key')); + if (! old('step',$request->validated('step'))) + abort(404); - $page_actions = collect(['edit'=>TRUE,'copy'=>TRUE]); + $key = $this->request_key($request,collect(old())); - return view('frames.dn') - ->with('o',config('server')->fetch($dn)) - ->with('dn',$dn) - ->with('page_actions',$page_actions); + $o = new Entry; + + if (count(array_filter($x=old('objectclass',$request->objectclass)))) { + $o->objectclass = $x; + + foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao) + $o->addAttribute($ao,''); + + $o->setRDNBase($key['dn']); + } + + $step = $request->step ? $request->step+1 : old('step'); + + return view('frame') + ->with('subframe','create') + ->with('bases',$this->bases()) + ->with('o',$o) + ->with('step',$step) + ->with('container',old('container',$key['dn'])); } /** @@ -75,7 +94,7 @@ class HomeController extends Controller * @param string $id * @return \Closure|\Illuminate\Contracts\View\View|string */ - public function entry_attr_add(Request $request,string $id) + public function entry_attr_add(Request $request,string $id): string { $xx = new \stdClass(); $xx->index = 0; @@ -90,6 +109,53 @@ class HomeController extends Controller return $x; } + public function entry_create(EntryAddRequest $request) + { + $key = $this->request_key($request,collect(old())); + + $dn = sprintf('%s=%s,%s',$request->rdn,$request->rdn_value,$key['dn']); + + $o = new Entry; + $o->setDn($dn); + + foreach ($request->except(['_token','key','step','rdn','rdn_value']) as $key => $value) + $o->{$key} = array_filter($value); + + try { + $o->save(); + + } catch (InsufficientAccessException $e) { + $request->flash(); + + switch ($x=$e->getDetailedError()->getErrorCode()) { + case 50: + return Redirect::to('/') + ->withInput() + ->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage()))); + + default: + abort(599,$e->getDetailedError()->getErrorMessage()); + } + + // @todo To test and valide this Exception is caught + } catch (LdapRecordException $e) { + $request->flash(); + + switch ($x=$e->getDetailedError()->getErrorCode()) { + case 8: + return Redirect::to('/') + ->withInput() + ->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage()))); + + default: + abort(599,$e->getDetailedError()->getErrorMessage()); + } + } + + return Redirect::to('/') + ->withFragment($o->getDNSecure()); + } + public function entry_export(Request $request,string $id) { $dn = Crypt::decryptString($id); @@ -112,11 +178,9 @@ class HomeController extends Controller * @param string $id * @return mixed */ - public function entry_objectclass_add(string $id) + public function entry_objectclass_add(Request $request) { - $dn = Crypt::decryptString($id); - $o = config('server')->fetch($dn); - $oc = $o->getObject('objectclass'); + $oc = Factory::create('objectclass',$request->oc); $ocs = $oc ->structural @@ -259,26 +323,51 @@ class HomeController extends Controller } /** - * Application home page + * Render a frame, normally as a result of an AJAX call + * This will render the right frame. + * + * @param Request $request + * @param Collection|null $old + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|View */ - public function home() + public function frame(Request $request,?Collection $old=NULL): View { - if (old('dn')) - return view('frame') - ->with('subframe','dn') - ->with('bases',$this->bases()) - ->with('o',config('server')->fetch($dn=Crypt::decryptString(old('dn')))) - ->with('dn',$dn); + // If our index was not render from a root url, then redirect to it + if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST') + abort(409); - elseif (old('frame')) - return view('frame') - ->with('subframe',old('frame')) + $key = $this->request_key($request,$old); + + $view = ($old + ? view('frame')->with('subframe',$key['cmd']) + : view('frames.'.$key['cmd'])) + ->with('bases',$this->bases()); + + return match ($key['cmd']) { + 'create' => $view + ->with('container',old('container',$key['dn'])) + ->with('step',1), + + 'dn' => $view + ->with('dn',$key['dn']) + ->with('page_actions',collect(['edit'=>TRUE,'copy'=>TRUE])), + + 'import' => $view, + + default => abort(404), + }; + } + + /** + * This is the main page render function + */ + public function home(Request $request) + { + // Did we come here as a result of a redirect + return count(old()) + ? $this->frame($request,collect(old())) + : view('home') ->with('bases',$this->bases()); - - else - return view('home') - ->with('bases',$this->bases()) - ->with('server',config('ldap.connections.default.name')); } /** @@ -334,6 +423,39 @@ class HomeController extends Controller ->with('s',config('server')); } + /** + * For any incoming request, work out the command and DN involved + * + * @param Request $request + * @param Collection|null $old + * @return array + */ + private function request_key(Request $request,?Collection $old=NULL): array + { + // Setup + $cmd = NULL; + $dn = NULL; + $key = $request->get('key',old('key')) + ? Crypt::decryptString($request->get('key',old('key'))) + : NULL; + + // Determine if our key has a command + if (str_contains($key,'|')) { + $m = []; + + if (preg_match('/\*([a-z_]+)\|(.+)$/',$key,$m)) { + $cmd = $m[1]; + $dn = ($m[2] !== '_NOP') ? $m[2] : NULL; + } + + } elseif (old('dn',$request->get('key'))) { + $cmd = 'dn'; + $dn = Crypt::decryptString(old('dn',$request->get('key'))); + } + + return ['cmd'=>$cmd,'dn'=>$dn]; + } + /** * Show the Schema Viewer * diff --git a/app/Http/Requests/EntryAddRequest.php b/app/Http/Requests/EntryAddRequest.php new file mode 100644 index 00000000..dc2b865d --- /dev/null +++ b/app/Http/Requests/EntryAddRequest.php @@ -0,0 +1,71 @@ + + */ + public function messages(): array + { + return [ + 'rdn' => __('RDN is required.'), + 'rdn_value' => __('RDN value is required.'), + ]; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + public function rules(): array + { + if (request()->method() === 'GET') + return []; + + return config('server') + ->schema('attributetypes') + ->intersectByKeys($this->request) + ->map(fn($item)=>$item->validation(request()->get('objectclass'))) + ->filter() + ->flatMap(fn($item)=>$item) + ->merge([ + 'key' => [ + 'required', + new DNExists, + function (string $attribute,mixed $value,\Closure $fail) { + $cmd = Crypt::decryptString($value); + + // Sometimes our key has a command, so we'll ignore it + if (str_starts_with($cmd,'*') && ($x=strpos($cmd,'|'))) + $cmd = substr($cmd,1,$x-1); + + if ($cmd !== 'create') { + $fail(sprintf('Invalid command: %s',$cmd)); + } + }, + ], + 'rdn' => 'required_if:step,2|string|min:1', + 'rdn_value' => 'required_if:step,2|string|min:1', + 'step' => 'int|min:1|max:2', + 'objectclass'=>[ + 'required', + 'array', + 'min:1', + new HasStructuralObjectClass, + ] + ]) + ->toArray(); + } +} \ No newline at end of file diff --git a/app/Http/Requests/EntryRequest.php b/app/Http/Requests/EntryRequest.php index c454aa9b..b7b0dd1c 100644 --- a/app/Http/Requests/EntryRequest.php +++ b/app/Http/Requests/EntryRequest.php @@ -6,27 +6,17 @@ use Illuminate\Foundation\Http\FormRequest; class EntryRequest extends FormRequest { - /** - * Determine if the user is authorized to make this request. - * - * @return bool - */ - public function authorize() - { - return TRUE; - } - /** * Get the validation rules that apply to the request. * * @return array */ - public function rules() + public function rules(): array { return config('server') ->schema('attributetypes') ->intersectByKeys($this->request) - ->map(fn($item)=>$item->validation(request()->get('objectclass'))) + ->map(fn($item)=>$item->validation(request()?->get('objectclass') ?: [])) ->filter() ->flatMap(fn($item)=>$item) ->toArray(); diff --git a/app/Http/Requests/ImportRequest.php b/app/Http/Requests/ImportRequest.php index abdf8c16..8983aa47 100644 --- a/app/Http/Requests/ImportRequest.php +++ b/app/Http/Requests/ImportRequest.php @@ -6,15 +6,9 @@ use Illuminate\Foundation\Http\FormRequest; class ImportRequest extends FormRequest { - public function authorize() - { - return TRUE; - } - - public function rules() + public function rules(): array { return [ - 'frame' => 'required|string|in:import', 'file' => 'nullable|extensions:ldif|required_without:text', 'text'=> 'nullable|prohibits:file|string|min:16', ]; diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 2bc93762..cb25f6b9 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -12,11 +12,14 @@ use App\Classes\LDAP\Attribute; use App\Classes\LDAP\Attribute\Factory; use App\Classes\LDAP\Export\LDIF; use App\Exceptions\Import\AttributeException; +use App\Exceptions\InvalidUsage; class Entry extends Model { private Collection $objects; private bool $noObjectAttributes = FALSE; + // For new entries, this is the container that this entry will be stored in + private string $rdnbase; /* OVERRIDES */ @@ -46,7 +49,7 @@ class Entry extends Model public function getAttributes(): array { return $this->objects - ->map(fn($item)=>$item->values->toArray()) + ->map(fn($item)=>$item->values) ->toArray(); } @@ -92,10 +95,7 @@ class Entry extends Model $key = $this->normalizeAttributeKey($key); if ((! $this->objects->get($key)) && $value) { - $o = new Attribute($key,[]); - $o->value = $value; - - $this->objects->put($key,$o); + $this->objects->put($key,Factory::create($key,$value)); } elseif ($this->objects->get($key)) { $this->objects->get($key)->value = $this->attributes[$key]; @@ -265,8 +265,12 @@ class Entry extends Model */ public function getObject(string $key): Attribute|null { - return $this->objects - ->get($this->normalizeAttributeKey($key)); + return match ($key) { + 'rdn' => $this->getRDNObject(), + + default => $this->objects + ->get($this->normalizeAttributeKey($key)) + }; } public function getObjects(): Collection @@ -289,6 +293,16 @@ class Entry extends Model ->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name)))); } + private function getRDNObject(): Attribute\RDN + { + $o = new Attribute\RDN('dn',['']); + // @todo for an existing object, return the base. + $o->setBase($this->rdnbase); + $o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required)); + + return $o; + } + /** * Return this list of user attributes * @@ -413,4 +427,12 @@ class Entry extends Model return $this; } + + public function setRDNBase(string $bdn): void + { + if ($this->exists) + throw new InvalidUsage('Cannot set RDN base on existing entries'); + + $this->rdnbase = $bdn; + } } \ No newline at end of file diff --git a/app/Rules/DNExists.php b/app/Rules/DNExists.php new file mode 100644 index 00000000..9048b8ea --- /dev/null +++ b/app/Rules/DNExists.php @@ -0,0 +1,27 @@ +fetch($dn)) + $fail(sprintf('The DN %s doesnt exist.',$dn)); + } +} diff --git a/app/Rules/HasStructuralObjectClass.php b/app/Rules/HasStructuralObjectClass.php new file mode 100644 index 00000000..04e7efe7 --- /dev/null +++ b/app/Rules/HasStructuralObjectClass.php @@ -0,0 +1,23 @@ +schema('objectclasses',$item)->isStructural()) + return; + + $fail('There isnt a Structural Objectclass.'); + } +} diff --git a/public/css/custom.css b/public/css/custom.css index 3030a2a1..77f1db97 100644 --- a/public/css/custom.css +++ b/public/css/custom.css @@ -7,9 +7,12 @@ img.jpegphoto { /** ensure our userpassword has select is next to the password input */ div#userPassword .select2-container--bootstrap-5 .select2-selection { font-size: inherit; - border-bottom-right-radius: unset; - border-top-right-radius: unset; width: 9em; border: #444054 1px solid; background-color: #f0f0f0; +} + +.input-group:first-child .select2-container--bootstrap-5 .select2-selection { + border-bottom-right-radius: unset; + border-top-right-radius: unset; } \ No newline at end of file diff --git a/public/css/fixes.css b/public/css/fixes.css index f34e96ef..1c02a97d 100644 --- a/public/css/fixes.css +++ b/public/css/fixes.css @@ -303,4 +303,8 @@ div#objectClass .input-group-delete { bottom: 30px; right: 10px; height: 5px; +} + +.input-group-text { + background-color: #fafafa; } \ No newline at end of file diff --git a/public/js/custom.js b/public/js/custom.js index cab584ac..e49996a9 100644 --- a/public/js/custom.js +++ b/public/js/custom.js @@ -13,32 +13,41 @@ function expandChildren(node) { function getNode(item) { $.ajax({ - url: '/dn', + url: '/frame', method: 'POST', data: { key: item }, dataType: 'html', beforeSend: function() { - content = $('.main-content').contents(); - $('.main-content').empty().append('
'); + content = $('.main-content') + .contents(); + + $('.main-content') + .empty() + .append('
'); } }).done(function(html) { - $('.main-content').empty().append(html); + $('.main-content') + .empty() + .append(html); - }).fail(function(item) { - switch(item.status) { + }).fail(function(e) { + switch(e.status) { case 404: - $('.main-content').empty().append(item.responseText); + $('.main-content').empty().append(e.responseText); + break; + case 409: + location.replace('/#'+item); break; case 419: alert('Session has expired, reloading the page and try again...'); location.reload(); break; case 500: - $('.main-content').empty().append(item.responseText); + $('.main-content').empty().append(e.responseText); break; default: - alert(item.status+': Well that didnt work?'); + alert('Well that didnt work? Code ['+e.status+']'); } }); } diff --git a/resources/views/components/attribute/rdn.blade.php b/resources/views/components/attribute/rdn.blade.php new file mode 100644 index 00000000..afb6f1d7 --- /dev/null +++ b/resources/views/components/attribute/rdn.blade.php @@ -0,0 +1,64 @@ + + + @foreach($o->values as $value) + @if($edit) +
+ + + = + $errors->get('rdn_value')]) id="rdn_value" name="rdn_value" value="{{ old('rdn_value') }}" placeholder="rdn"> + + +
+ @error('rdn') + {{ $message }} + @enderror + @error('rdn_value') + {{ $message }} + @enderror +
+
+ @else + {{ $value }} + @endif + @endforeach +
+ +@section('page-scripts') + +@endsection \ No newline at end of file diff --git a/resources/views/components/attribute/widget/options.blade.php b/resources/views/components/attribute/widget/options.blade.php index fdf7aa8d..90eb545b 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -46,12 +46,15 @@ if (! rendered) $.ajax({ type: 'POST', - // @todo When this is opened a second time, the data is appended. + cache: false, + url: '{{ url('entry/objectclass/add') }}', + data: { + oc: oc, + }, success: function(data) { $('select#newoc').select2({ dropdownParent: $('#new_objectclass-modal'), theme: 'bootstrap-5', - allowClear: true, multiple: true, data: data, }); @@ -60,8 +63,6 @@ if (e.status != 412) alert('That didnt work? Please try again....'); }, - url: '{{ url('entry/objectclass/add') }}/'+dn, - cache: false }); rendered = true; diff --git a/resources/views/components/form/base.blade.php b/resources/views/components/form/base.blade.php index 7426f1ec..1aac32f0 100644 --- a/resources/views/components/form/base.blade.php +++ b/resources/views/components/form/base.blade.php @@ -15,7 +15,7 @@ {{ $slot }} @isset($name) - @error((! empty($old)) ? $old : $name) + @error((! empty($old)) ? $old : ($id ?? $name)) {{ $message }} @elseif(isset($feedback)) {{ $feedback }} diff --git a/resources/views/components/form/select.blade.php b/resources/views/components/form/select.blade.php index 23cb1489..c01d4fb7 100644 --- a/resources/views/components/form/select.blade.php +++ b/resources/views/components/form/select.blade.php @@ -2,7 +2,7 @@ @isset($name) @endisset - @if((empty($value) && ! empty($options)) || isset($addnew) || isset($choose)) @isset($addnew) @@ -54,10 +54,24 @@ width: 'style', allowClear: {{ $allowclear ?? 'false' }}, placeholder: '{{ $placeholder ?? '' }}', + multiple: {{ $multiple ?? 'false' }}, @isset($addvalues) tags: true, @endisset }); + + @if(isset($multiple) && (! $multiple)) + $('#{{ $id ?? $name }}').val(' '); + $('#{{ $id ?? $name }}').trigger('change'); + @endif + + @isset($options) + @if($options->count() === 1) + $('#{{ $id ?? $name }}') + .val('{{ $options->first()['id'] }}') + .trigger("change") + @endif + @endisset }); @append \ No newline at end of file diff --git a/resources/views/fragment/dn/add_attr.blade.php b/resources/views/fragment/dn/add_attr.blade.php new file mode 100644 index 00000000..3a20a1e5 --- /dev/null +++ b/resources/views/fragment/dn/add_attr.blade.php @@ -0,0 +1,22 @@ +
+ + +
+
+
+
+
+
+ Add New Attribute +
+
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/resources/views/fragment/dn/header.blade.php b/resources/views/fragment/dn/header.blade.php index 020a3cc7..cf221d76 100644 --- a/resources/views/fragment/dn/header.blade.php +++ b/resources/views/fragment/dn/header.blade.php @@ -3,7 +3,7 @@ {!! $x ? $x->render(FALSE,TRUE) : sprintf('
',$o->icon() ?? "fas fa-info") !!} - {{ $dn }} + {{ $o->getDn() }} diff --git a/resources/views/frames/create.blade.php b/resources/views/frames/create.blade.php new file mode 100644 index 00000000..0063e0c6 --- /dev/null +++ b/resources/views/frames/create.blade.php @@ -0,0 +1,123 @@ +@extends('layouts.dn') + +@section('page_title') + @include('fragment.dn.header',['o'=>($oo=config('server')->fetch(old('container',$container)))]) +@endsection + +@section('main-content') + + +
+
+
+ +
+ @lang('Create New Entry') - @lang('Step') {{ $step }} +
+ +
+
+ @csrf + + + + + @switch($step) + @case(1) +
+
+ +
+
+ @break + + @case(2) + + + @foreach ($o->getVisibleAttributes() as $ao) + + @endforeach + + @include('fragment.dn.add_attr') + + @break; + @endswitch + + +
+
+ + +
+
+
+
+
+
+@endsection + +@section('page-scripts') + +@append \ No newline at end of file diff --git a/resources/views/frames/dn.blade.php b/resources/views/frames/dn.blade.php index 79014c1d..cdf8b5b9 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -1,7 +1,7 @@ @extends('layouts.dn') @section('page_title') - @include('fragment.dn.header') + @include('fragment.dn.header',['o'=>($o=config('server')->fetch($dn))]) @endsection @section('main-content') @@ -9,8 +9,6 @@ - -
@@ -34,31 +32,7 @@ @endforeach -
- - -
-
-
-
- - @if($o->getMissingAttributes()->count()) -
-
- Add New Attribute -
-
- -
-
- -
-
- @endif -
-
-
-
+ @include('fragment.dn.add_attr')
diff --git a/resources/views/frames/import.blade.php b/resources/views/frames/import.blade.php index 8e36a91f..73f6220b 100644 --- a/resources/views/frames/import.blade.php +++ b/resources/views/frames/import.blade.php @@ -15,7 +15,7 @@
@csrf - +
@lang('LDIF Import') diff --git a/resources/views/frames/schema.blade.php b/resources/views/frames/schema.blade.php index e12ec43a..37245b9f 100644 --- a/resources/views/frames/schema.blade.php +++ b/resources/views/frames/schema.blade.php @@ -1,10 +1,11 @@ +@use(App\Classes\LDAP\Server) @extends('layouts.dn') @section('page_title') - +
{{ \App\Classes\LDAP\Server::schemaDN() }}{{ Server::schemaDN() }}
@endsection diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 7eb82ac8..9ce12227 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -17,7 +17,7 @@

phpLDAPadmin

- PLA Logo + PLA Logo
@@ -48,14 +48,23 @@ var subpage = window.location.hash; $(document).ready(function() { - // Enable navigating to a page via a URL fragment, and that fragment is defined with a server-icon - var valid = Object.values($('.server-icon > a').map(function(item) { - return $(this).attr('id'); - })).indexOf(subpage.substring(1)); + if (subpage) { + // Enable navigating to a page via a URL fragment, and that fragment is defined with a server-icon + var valid = Object.values($('.server-icon > a').map(function() { + return $(this).attr('id'); + })).indexOf(subpage.substring(1)); - if (valid !== -1 && subpage) { - // The click() event wont have been registered yet, so we need to delay us clicking it - setTimeout(function() { $(subpage).click(); },250); + if (valid !== -1) { + // @todo this condition can probably be removed + console.log('teleporting...:'+subpage.substring(1)); + // The click() event wont have been registered yet, so we need to delay us clicking it + setTimeout(function() { $(subpage).click(); },250); + + } else if (valid === -1) { + // Clear the hash + history.replaceState(null,null,' '); + getNode(subpage.substring(1)); + } } }); diff --git a/routes/web.php b/routes/web.php index 1fa1dcac..7828f817 100644 --- a/routes/web.php +++ b/routes/web.php @@ -32,8 +32,8 @@ Route::controller(HomeController::class)->group(function() { Route::middleware(AllowAnonymous::class)->group(function() { Route::get('/','home'); Route::get('info','info'); - Route::post('dn','dn_frame'); Route::get('debug','debug'); + Route::post('frame','frame'); Route::get('import','import_frame'); Route::get('schema','schema_frame'); @@ -41,10 +41,12 @@ Route::controller(HomeController::class)->group(function() { Route::get('image','user_image'); }); + Route::match(['get','post'],'entry/add','entry_add'); + Route::post('entry/create','entry_create'); Route::get('entry/export/{id}','entry_export'); Route::post('entry/password/check/','entry_password_check'); Route::post('entry/attr/add/{id}','entry_attr_add'); - Route::post('entry/objectclass/add/{id}','entry_objectclass_add'); + Route::post('entry/objectclass/add','entry_objectclass_add'); Route::post('entry/update/commit','entry_update'); Route::post('entry/update/pending','entry_pending_update');