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..18311dbb --- /dev/null +++ b/app/Classes/LDAP/Attribute/RDN.php @@ -0,0 +1,51 @@ + $this->base, + 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 setBase(string $base): void + { + $this->base = $base; + } + + public function setObjectClasses(array $classes): void + { + $this->objectclasses = collect(); + + foreach ($classes as $class) + $this->objectclasses->push(cofig('server')->schema('objectclasses',$class)); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/APIController.php b/app/Http/Controllers/APIController.php index f2283029..88aa3505 100644 --- a/app/Http/Controllers/APIController.php +++ b/app/Http/Controllers/APIController.php @@ -22,15 +22,15 @@ class APIController extends Controller { $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(), + ]); } /** @@ -45,15 +45,22 @@ class APIController extends Controller 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_new',$dn)), + 'lazy'=>FALSE, + 'icon'=>'fas fa-fw fa-square-plus text-warning', + 'tooltip'=>__('Create new LDAP item here'), + ]); } public function schema_view(Request $request) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 74cb4a34..18b2e4a2 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,6 +2,9 @@ namespace App\Http\Controllers; +use Illuminate\Contracts\View\Factory; +use Illuminate\Contracts\View\View; +use Illuminate\Foundation\Application; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -12,16 +15,16 @@ use Illuminate\Support\Facades\Redirect; use LdapRecord\Exceptions\InsufficientAccessException; use LdapRecord\LdapRecordException; use LdapRecord\Query\ObjectNotFoundException; +use Nette\NotImplementedException; 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\{EntryAdd,EntryRequest,ImportRequest}; use App\Ldap\Entry; use App\View\Components\AttributeType; -use Nette\NotImplementedException; class HomeController extends Controller { @@ -59,13 +62,57 @@ class HomeController extends Controller public function dn_frame(Request $request) { $dn = Crypt::decryptString($request->post('key')); + $cmd = ''; - $page_actions = collect(['edit'=>TRUE,'copy'=>TRUE]); + if (str_contains($dn,'|')) { + $m = []; - return view('frames.dn') - ->with('o',config('server')->fetch($dn)) - ->with('dn',$dn) - ->with('page_actions',$page_actions); + if (preg_match('/\*([a-z_]+)\|(.+)$/',$dn,$m)) { + $cmd = $m[1]; + $dn = $m[2]; + } + } + + return match ($cmd) { + 'create_new' => view('frames.create') + ->with('o',config('server')->fetch($dn)) + ->with('step',0) + ->with('dn',$dn), + + default => view('frames.dn') + ->with('o',config('server')->fetch($dn)) + ->with('dn',$dn) + ->with('page_actions',collect(['edit'=>TRUE,'copy'=>TRUE])), + }; + } + + /** + * Create a new object in the LDAP server + * + * @param EntryAdd $request + * @return Factory|View|Application|object + * @throws InvalidUsage + */ + public function entry_add(EntryAdd $request) + { + switch ($request->step) { + case 1: + $container = Crypt::decryptString($request->dn); + + $o = new Entry; + $o->objectclass = $request->objectclass; + $o->setRDNBase($container); + + return view('frame') + ->with('subframe',$request->frame) + ->with('bases',$this->bases()) + ->with('o',$o) + ->with('step',$request->step) + ->with('dn',$container); + + default: + throw new InvalidUsage('Invalid entry step'); + } } /** @@ -259,26 +306,30 @@ class HomeController extends Controller } /** - * Application home page + * This is the main page render function + * + * If a DN is set, when render a DN info/edit frame + * If a frame is set, then we render that (sub)frame */ public function home() { - if (old('dn')) + if (old('frame')) + return view('frame') + ->with('subframe',old('frame')) + ->with('bases',$this->bases()) + ->with('o',old('dn') ? config('server')->fetch($dn=Crypt::decryptString(old('dn'))) : NULL) + ->with('dn',$dn ?? NULL); + + elseif (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); - elseif (old('frame')) - return view('frame') - ->with('subframe',old('frame')) - ->with('bases',$this->bases()); - else return view('home') - ->with('bases',$this->bases()) - ->with('server',config('ldap.connections.default.name')); + ->with('bases',$this->bases()); } /** diff --git a/app/Http/Requests/EntryAdd.php b/app/Http/Requests/EntryAdd.php new file mode 100644 index 00000000..b5fc92c8 --- /dev/null +++ b/app/Http/Requests/EntryAdd.php @@ -0,0 +1,33 @@ + + */ + public function rules(): array + { + return [ + 'dn' => new DNExists, + 'objectclass' => [ + 'required', + 'string', + new StructuralObjectClass, + ], + 'step' => 'int|min:1|max:2', + 'frame' => [ + 'string', + Rule::in(['create']), + ] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/EntryRequest.php b/app/Http/Requests/EntryRequest.php index 5aba0c77..c8927bd6 100644 --- a/app/Http/Requests/EntryRequest.php +++ b/app/Http/Requests/EntryRequest.php @@ -6,22 +6,12 @@ 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') diff --git a/app/Http/Requests/ImportRequest.php b/app/Http/Requests/ImportRequest.php index abdf8c16..5f5ffac9 100644 --- a/app/Http/Requests/ImportRequest.php +++ b/app/Http/Requests/ImportRequest.php @@ -6,12 +6,7 @@ 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', diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 2bc93762..c6fbdc54 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,14 @@ 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',[' ']); + $o->setBase($this->rdnbase); // @todo for an existing object, return the base. + + return $o; + } + /** * Return this list of user attributes * @@ -413,4 +425,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..a3692748 --- /dev/null +++ b/app/Rules/DNExists.php @@ -0,0 +1,21 @@ +fetch($x=Crypt::decryptString($value))) + $fail(sprintf('The DN %s doesnt exist.',$x)); + } +} diff --git a/app/Rules/StructuralObjectClass.php b/app/Rules/StructuralObjectClass.php new file mode 100644 index 00000000..c346503a --- /dev/null +++ b/app/Rules/StructuralObjectClass.php @@ -0,0 +1,20 @@ +schema('objectclasses',$value)->isStructural()) + $fail(sprintf('The object class %s is not a StructuralObject class.',$value)); + } +} 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/resources/views/components/attribute/rdn.blade.php b/resources/views/components/attribute/rdn.blade.php new file mode 100644 index 00000000..1e2e5343 --- /dev/null +++ b/resources/views/components/attribute/rdn.blade.php @@ -0,0 +1,25 @@ + + + @foreach($o->values as $value) + @if($edit) +
+ + + = + ($e=$errors->get($o->name_lc.'.'.$loop->index)),'border-focus'=>$o->values->contains($value)]) name="rdn" placeholder="rdn"> + + +
+ @if($e) + {{ join('|',$e) }} + @endif +
+
+ @else + {{ $value }} + @endif + @endforeach +
\ 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..d18fcab2 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -51,7 +51,6 @@ $('select#newoc').select2({ dropdownParent: $('#new_objectclass-modal'), theme: 'bootstrap-5', - allowClear: true, multiple: true, data: data, }); diff --git a/resources/views/components/form/select.blade.php b/resources/views/components/form/select.blade.php index 23cb1489..02a9469a 100644 --- a/resources/views/components/form/select.blade.php +++ b/resources/views/components/form/select.blade.php @@ -54,10 +54,14 @@ width: 'style', allowClear: {{ $allowclear ?? 'false' }}, placeholder: '{{ $placeholder ?? '' }}', + multiple: {{ $multiple ?? 'false' }}, @isset($addvalues) tags: true, @endisset }); + + $('#{{ $id ?? $name }}').val(' '); + $('#{{ $id ?? $name }}').trigger('change'); }); @append \ No newline at end of file diff --git a/resources/views/frames/create.blade.php b/resources/views/frames/create.blade.php new file mode 100644 index 00000000..53bf6587 --- /dev/null +++ b/resources/views/frames/create.blade.php @@ -0,0 +1,163 @@ +@extends('layouts.dn') + +@section('page_title') + @include('fragment.dn.header') +@endsection + +@section('main-content') +
+

+ This is a tech preview of what is to come, and it is by no means complete. +

+
+
+
+
+
+ @csrf + + + + +
+ @lang('Create New Entry') +
+ + @switch($step) + @case(1) +
+
+
+ +
+
+
+ @break + + @case(2) +
+ + + @foreach ($o->getVisibleAttributes() as $ao) + + @endforeach + +
+ + +
+
+
+
+ + @if($o->getMissingAttributes()->count()) +
+
+ Add New Attribute +
+
+ +
+
+ +
+
+ @endif +
+
+
+
+ +
+
+ + +
+
+
+ @break; + @endswitch + + +
+
+
+
+@endsection + +@section('page-scripts') + +@append \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 36e32ea6..59aa8417 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,6 +38,7 @@ Route::group(['prefix'=>'user'],function() { Route::get('image',[HomeController::class,'user_image']); }); +Route::post('entry/add',[HomeController::class,'entry_add']); Route::get('entry/export/{id}',[HomeController::class,'entry_export']); Route::post('entry/password/check/',[HomeController::class,'entry_password_check']); Route::post('entry/attr/add/{id}',[HomeController::class,'entry_attr_add']);