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
This commit is contained in:
2025-02-23 18:14:41 +11:00
parent f08fdb1bcd
commit 996d7bb1dc
27 changed files with 687 additions and 147 deletions

View File

@@ -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);

View File

@@ -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
*