Compare commits

...

9 Commits

Author SHA1 Message Date
9930479a07 Move direct controller direct view calls to route/web, add global $server to use in views, negating the need to use config('server')
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 4m8s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 2m0s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-14 17:12:24 +11:00
788f42ea9e Some DN rendering fixes, so that our Server Info renders correctly (aligned values) 2025-03-14 17:01:24 +11:00
4adcbeeefa Store our DN and objectclasses in Attribute::class entries, so that we can dynamically calculate is_rdn and required objects (to be implemented) 2025-03-14 17:01:24 +11:00
c03ebad60e When creating a new entry, and an RDN attribute has more than 1 input, only take over the first input when selecting the RDN attribute 2025-03-14 17:01:24 +11:00
b30f6fd050 Include LDAP diagnostic error message when we have an LDAP error 2025-03-14 17:01:24 +11:00
7ad224a5a4 Attribute cleanup and optimisation in preparation to support attribute tags, HomeController return casting 2025-03-14 17:01:24 +11:00
aac1e0479a Revert version to 2.0.3-dev 2025-03-13 23:25:14 +11:00
1ddb58ebbb Buttons that trigger ajax activity cant be buttons, change them back to span 2025-03-13 23:25:04 +11:00
b260912e01 Revert changing buttons in 49fd9b419a 2025-03-13 21:22:31 +11:00
30 changed files with 198 additions and 206 deletions

View File

@ -17,31 +17,31 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
protected string $name; protected string $name;
private int $counter = 0; private int $counter = 0;
protected ?AttributeType $schema = NULL;
/*
# Source of this attribute definition
protected $source;
*/
// Current and Old Values
protected Collection $values;
// Is this attribute an internal attribute // Is this attribute an internal attribute
protected bool $is_internal = FALSE; protected(set) bool $is_internal = FALSE;
// Is this attribute the RDN? // Is this attribute the RDN?
protected bool $is_rdn = FALSE; public bool $is_rdn = FALSE;
// MIN/MAX number of values // MIN/MAX number of values
protected int $min_values_count = 0; protected(set) int $min_values_count = 0;
protected int $max_values_count = 0; protected(set) int $max_values_count = 0;
// RFC3866 Language Tags // RFC3866 Language Tags
/* @deprecated use $values/$values_old when playing with language tags */
protected Collection $lang_tags; protected Collection $lang_tags;
// The schema's representation of this attribute
protected(set) ?AttributeType $schema;
// The DN this object is in
protected(set) string $dn;
// The old values for this attribute - helps with isDirty() to determine if there is an update pending // The old values for this attribute - helps with isDirty() to determine if there is an update pending
protected Collection $oldValues; protected(set) Collection $values_old;
// Current Values
public Collection $values;
// The other object classes of the entry that include this attribute
protected(set) Collection $oc;
/* /*
# Has the attribute been modified # Has the attribute been modified
@ -94,12 +94,23 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
protected $postvalue = array(); protected $postvalue = array();
*/ */
public function __construct(string $name,array $values) /**
* Create an Attribute
*
* @param string $dn DN this attribute is used in
* @param string $name Name of the attribute
* @param array $values Current Values
* @param array $oc ObjectClasses that the DN has, that includes this attribute
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{ {
$this->dn = $dn;
$this->name = $name; $this->name = $name;
$this->values = collect($values); $this->values_old = collect($values);
$this->values = collect();
$this->oc = collect($oc);
$this->lang_tags = collect(); $this->lang_tags = collect();
$this->oldValues = collect($values);
$this->schema = (new Server) $this->schema = (new Server)
->schema('attributetypes',$name); ->schema('attributetypes',$name);
@ -132,18 +143,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
'hints' => $this->hints(), 'hints' => $this->hints(),
// Can this attribute be edited // Can this attribute be edited
'is_editable' => $this->schema ? $this->schema->{$key} : NULL, 'is_editable' => $this->schema ? $this->schema->{$key} : NULL,
// Is this an internal attribute
'is_internal' => isset($this->{$key}) && $this->{$key},
// Is this attribute the RDN
'is_rdn' => $this->is_rdn,
// We prefer the name as per the schema if it exists // We prefer the name as per the schema if it exists
'name' => $this->schema ? $this->schema->{$key} : $this->{$key}, 'name' => $this->schema ? $this->schema->{$key} : $this->{$key},
// Attribute name in lower case // Attribute name in lower case
'name_lc' => strtolower($this->name), 'name_lc' => strtolower($this->name),
// Old Values
'old_values' => $this->oldValues,
// Attribute values
'values' => $this->values,
// Required by Object Classes // Required by Object Classes
'required_by' => $this->schema?->required_by_object_classes ?: collect(), 'required_by' => $this->schema?->required_by_object_classes ?: collect(),
// Used in Object Classes // Used in Object Classes
@ -156,11 +159,8 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
public function __set(string $key,mixed $values): void public function __set(string $key,mixed $values): void
{ {
switch ($key) { switch ($key) {
case 'value':
$this->values = collect($values);
break;
default: default:
dd(['key'=>$key,'values'=>$values]);
} }
} }
@ -260,13 +260,8 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/ */
public function isDirty(): bool public function isDirty(): bool
{ {
return ($this->oldValues->count() !== $this->values->count()) return ($this->values_old->count() !== $this->values->count())
|| ($this->values->diff($this->oldValues)->count() !== 0); || ($this->values->diff($this->values_old)->count() !== 0);
}
public function oldValues(array $array): void
{
$this->oldValues = collect($array);
} }
/** /**
@ -292,7 +287,7 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
public function render_item_old(int $key): ?string public function render_item_old(int $key): ?string
{ {
return Arr::get($this->old_values,$key); return Arr::get($this->values_old,$key);
} }
public function render_item_new(int $key): ?string public function render_item_new(int $key): ?string
@ -306,14 +301,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
* @param string $tag * @param string $tag
* @param array $value * @param array $value
* @return void * @return void
* @deprecated
*/ */
public function setLangTag(string $tag,array $value): void public function setLangTag(string $tag,array $value): void
{ {
$this->lang_tags->put($tag,$value); $this->lang_tags->put($tag,$value);
} }
public function setRDN(): void
{
$this->is_rdn = TRUE;
}
} }

View File

@ -7,6 +7,6 @@ use App\Classes\LDAP\Attribute;
/** /**
* Represents an attribute whose values are binary * Represents an attribute whose values are binary
*/ */
class Binary extends Attribute abstract class Binary extends Attribute
{ {
} }

View File

@ -49,15 +49,17 @@ class Factory
/** /**
* Create the new Object for an attribute * Create the new Object for an attribute
* *
* @param string $dn
* @param string $attribute * @param string $attribute
* @param array $values * @param array $values
* @param array $oc
* @return Attribute * @return Attribute
*/ */
public static function create(string $attribute,array $values): Attribute public static function create(string $dn,string $attribute,array $values,array $oc=[]): Attribute
{ {
$class = Arr::get(self::map,strtolower($attribute),Attribute::class); $class = Arr::get(self::map,strtolower($attribute),Attribute::class);
Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class)); Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class));
return new $class($attribute,$values); return new $class($dn,$attribute,$values,$oc);
} }
} }

View File

@ -11,7 +11,7 @@ use App\Classes\LDAP\Attribute;
*/ */
abstract class Internal extends Attribute abstract class Internal extends Attribute
{ {
protected bool $is_internal = TRUE; protected(set) bool $is_internal = TRUE;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{ {

View File

@ -26,7 +26,7 @@ final class KrbPrincipalKey extends Attribute
public function render_item_old(int $key): ?string public function render_item_old(int $key): ?string
{ {
$pw = Arr::get($this->oldValues,$key); $pw = Arr::get($this->values_old,$key);
return $pw return $pw
? str_repeat('*',16) ? str_repeat('*',16)
: NULL; : NULL;

View File

@ -11,7 +11,7 @@ use Illuminate\Support\Collection;
* Represents an attribute whose value is a Kerberos Ticket Flag * Represents an attribute whose value is a Kerberos Ticket Flag
* See RFC4120 * See RFC4120
*/ */
class KrbTicketFlags extends Attribute final class KrbTicketFlags extends Attribute
{ {
private const DISALLOW_POSTDATED = 0x00000001; private const DISALLOW_POSTDATED = 0x00000001;
private const DISALLOW_FORWARDABLE = 0x00000002; private const DISALLOW_FORWARDABLE = 0x00000002;

View File

@ -15,9 +15,17 @@ final class ObjectClass extends Attribute
// The schema ObjectClasses for this objectclass of a DN // The schema ObjectClasses for this objectclass of a DN
protected Collection $oc_schema; protected Collection $oc_schema;
public function __construct(string $name,array $values) /**
* Create an ObjectClass Attribute
*
* @param string $dn DN this attribute is used in
* @param string $name Name of the attribute
* @param array $values Current Values
* @param array $oc ObjectClasses that the DN has, that includes this attribute
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{ {
parent::__construct($name,$values); parent::__construct($dn,$name,$values,$oc);
$this->oc_schema = config('server') $this->oc_schema = config('server')
->schema('objectclasses') ->schema('objectclasses')

View File

@ -87,7 +87,7 @@ final class Password extends Attribute
public function render_item_old(int $key): ?string public function render_item_old(int $key): ?string
{ {
$pw = Arr::get($this->oldValues,$key); $pw = Arr::get($this->values_old,$key);
return $pw return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16)) ? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
: NULL; : NULL;

View File

@ -3,9 +3,9 @@
namespace App\Classes\LDAP; namespace App\Classes\LDAP;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use LdapRecord\LdapRecordException;
use App\Exceptions\Import\GeneralException; use App\Exceptions\Import\GeneralException;
use App\Exceptions\Import\ObjectExistsException;
use App\Ldap\Entry; use App\Ldap\Entry;
/** /**
@ -48,7 +48,6 @@ abstract class Import
* @param int $action * @param int $action
* @return Collection * @return Collection
* @throws GeneralException * @throws GeneralException
* @throws ObjectExistsException
*/ */
final protected function commit(Entry $o,int $action): Collection final protected function commit(Entry $o,int $action): Collection
{ {
@ -57,7 +56,8 @@ abstract class Import
try { try {
$o->save(); $o->save();
} catch (\Exception $e) { } catch (LdapRecordException $e) {
if ($e->getDetailedError())
return collect([ return collect([
'dn'=>$o->getDN(), 'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s (%s)', 'result'=>sprintf('%d: %s (%s)',
@ -66,6 +66,14 @@ abstract class Import
$x->getDiagnosticMessage(), $x->getDiagnosticMessage(),
) )
]); ]);
else
return collect([
'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s',
$e->getCode(),
$e->getMessage(),
)
]);
} }
return collect(['dn'=>$o->getDN(),'result'=>__('Created')]); return collect(['dn'=>$o->getDN(),'result'=>__('Created')]);

View File

@ -46,7 +46,7 @@ class LDIF extends Import
if (! $line) { if (! $line) {
if (! is_null($o)) { if (! is_null($o)) {
// Add the last attribute; // Add the last attribute;
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value); $o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN())); Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
@ -125,7 +125,7 @@ class LDIF extends Import
Log::debug(sprintf('%s: Adding Attribute [%s] value [%s] (%d)',self::LOGKEY,$attribute,$value,$c)); Log::debug(sprintf('%s: Adding Attribute [%s] value [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
if ($value) if ($value)
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value); $o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
else else
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c)); throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
} }
@ -147,7 +147,7 @@ class LDIF extends Import
// We may still have a pending action // We may still have a pending action
if ($action) { if ($action) {
// Add the last attribute; // Add the last attribute;
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value); $o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN())); Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -42,24 +41,14 @@ class HomeController extends Controller
}); });
} }
/**
* Debug Page
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function debug()
{
return view('debug');
}
/** /**
* Create a new object in the LDAP server * Create a new object in the LDAP server
* *
* @param EntryAddRequest $request * @param EntryAddRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View * @return View
* @throws InvalidUsage * @throws InvalidUsage
*/ */
public function entry_add(EntryAddRequest $request) public function entry_add(EntryAddRequest $request): \Illuminate\View\View
{ {
if (! old('step',$request->validated('step'))) if (! old('step',$request->validated('step')))
abort(404); abort(404);
@ -72,7 +61,7 @@ class HomeController extends Controller
$o->objectclass = $x; $o->objectclass = $x;
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao) foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->addAttribute($ao,''); $o->{$ao->name} = '';
$o->setRDNBase($key['dn']); $o->setRDNBase($key['dn']);
} }
@ -92,24 +81,24 @@ class HomeController extends Controller
* *
* @param Request $request * @param Request $request
* @param string $id * @param string $id
* @return \Closure|\Illuminate\Contracts\View\View|string * @return \Illuminate\View\View
*/ */
public function entry_attr_add(Request $request,string $id): string public function entry_attr_add(Request $request,string $id): \Illuminate\View\View
{ {
$xx = new \stdClass; $xx = new \stdClass;
$xx->index = 0; $xx->index = 0;
$x = $request->noheader $dn = $request->dn ? Crypt::decrypt($request->dn) : '';
? (string)view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($id,[])) return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($dn,$id,[],$request->oc ?: []))
->with('value',$request->value) ->with('value',$request->value)
->with('loop',$xx) ->with('loop',$xx)
: (new AttributeType(Factory::create($id,[]),TRUE,collect($request->oc ?: [])))->render(); : (new AttributeType(Factory::create($dn,$id,[],$request->oc ?: []),TRUE,collect($request->oc ?: [])))->render();
return $x;
} }
public function entry_create(EntryAddRequest $request) public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
{ {
$key = $this->request_key($request,collect(old())); $key = $this->request_key($request,collect(old()));
@ -139,24 +128,21 @@ class HomeController extends Controller
// @todo when we create an entry, and it already exists, enable a redirect to it // @todo when we create an entry, and it already exists, enable a redirect to it
} catch (LdapRecordException $e) { } catch (LdapRecordException $e) {
$request->flash(); return Redirect::back()
switch ($x=$e->getDetailedError()->getErrorCode()) {
case 8:
return Redirect::to('/')
->withInput() ->withInput()
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage()))); ->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
default: $e->getDetailedError()->getErrorCode(),
abort(599,$e->getDetailedError()->getErrorMessage()); __($e->getDetailedError()->getErrorMessage()),
} $e->getDetailedError()->getDiagnosticMessage(),
));
} }
return Redirect::to('/') return Redirect::to('/')
->withFragment($o->getDNSecure()); ->withFragment($o->getDNSecure());
} }
public function entry_delete(Request $request) public function entry_delete(Request $request): \Illuminate\Http\RedirectResponse
{ {
$dn = Crypt::decryptString($request->dn); $dn = Crypt::decryptString($request->dn);
@ -196,7 +182,7 @@ class HomeController extends Controller
->with('success',[sprintf('%s: %s',__('Deleted'),$dn)]); ->with('success',[sprintf('%s: %s',__('Deleted'),$dn)]);
} }
public function entry_export(Request $request,string $id) public function entry_export(Request $request,string $id): \Illuminate\View\View
{ {
$dn = Crypt::decryptString($id); $dn = Crypt::decryptString($id);
@ -215,12 +201,13 @@ class HomeController extends Controller
/** /**
* Render an available list of objectclasses for an Entry * Render an available list of objectclasses for an Entry
* *
* @param string $id * @param Request $request
* @return mixed * @return Collection
*/ */
public function entry_objectclass_add(Request $request) public function entry_objectclass_add(Request $request): Collection
{ {
$oc = Factory::create('objectclass',$request->oc); $dn = $request->key ? Crypt::decryptString($request->dn) : '';
$oc = Factory::create($dn,'objectclass',$request->oc);
$ocs = $oc $ocs = $oc
->structural ->structural
@ -242,7 +229,7 @@ class HomeController extends Controller
]); ]);
} }
public function entry_password_check(Request $request) public function entry_password_check(Request $request): Collection
{ {
$dn = Crypt::decryptString($request->dn); $dn = Crypt::decryptString($request->dn);
$o = config('server')->fetch($dn); $o = config('server')->fetch($dn);
@ -265,10 +252,10 @@ class HomeController extends Controller
* Show a confirmation to update a DN * Show a confirmation to update a DN
* *
* @param EntryRequest $request * @param EntryRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
* @throws ObjectNotFoundException * @throws ObjectNotFoundException
*/ */
public function entry_pending_update(EntryRequest $request) public function entry_pending_update(EntryRequest $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
{ {
$dn = Crypt::decryptString($request->dn); $dn = Crypt::decryptString($request->dn);
@ -277,6 +264,8 @@ class HomeController extends Controller
foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value) foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value)
$o->{$key} = array_filter($value,fn($item)=>! is_null($item)); $o->{$key} = array_filter($value,fn($item)=>! is_null($item));
// @todo Need to handle incoming attributes that were modified by MD5Updates Trait (eg: jpegphoto)
// We need to process and encrypt the password // We need to process and encrypt the password
if ($request->userpassword) { if ($request->userpassword) {
$passwords = []; $passwords = [];
@ -312,8 +301,10 @@ class HomeController extends Controller
* @param EntryRequest $request * @param EntryRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws ObjectNotFoundException * @throws ObjectNotFoundException
* @todo When removing an attribute value, from a multi-value attribute, we have a ghost record showing after the update
* @todo Need to check when removing a single attribute value, do we have a ghost as well? Might be because we are redirecting with input?
*/ */
public function entry_update(EntryRequest $request) public function entry_update(EntryRequest $request): \Illuminate\Http\RedirectResponse
{ {
$dn = Crypt::decryptString($request->dn); $dn = Crypt::decryptString($request->dn);
@ -344,17 +335,14 @@ class HomeController extends Controller
} }
} catch (LdapRecordException $e) { } catch (LdapRecordException $e) {
$request->flash();
switch ($x=$e->getDetailedError()->getErrorCode()) {
case 8:
return Redirect::to('/') return Redirect::to('/')
->withInput() ->withInput()
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage()))); ->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
default: $e->getDetailedError()->getErrorCode(),
abort(599,$e->getDetailedError()->getErrorMessage()); __($e->getDetailedError()->getErrorMessage()),
} $e->getDetailedError()->getDiagnosticMessage(),
));
} }
return Redirect::to('/') return Redirect::to('/')
@ -368,9 +356,9 @@ class HomeController extends Controller
* *
* @param Request $request * @param Request $request
* @param Collection|null $old * @param Collection|null $old
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|View * @return \Illuminate\View\View
*/ */
public function frame(Request $request,?Collection $old=NULL): View public function frame(Request $request,?Collection $old=NULL): \Illuminate\View\View
{ {
// If our index was not render from a root url, then redirect to it // If our index was not render from a root url, then redirect to it
if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST') if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST')
@ -385,7 +373,9 @@ class HomeController extends Controller
// If we are rendering a DN, rebuild our object // If we are rendering a DN, rebuild our object
$o = config('server')->fetch($key['dn']); $o = config('server')->fetch($key['dn']);
foreach (collect(old())->except(['dn','_token']) as $attr => $value)
// @todo We need to dynamically exclude request items, so we dont need to add them here
foreach (collect(old())->except(['dn','_token','userpassword_hash']) as $attr => $value)
$o->{$attr} = $value; $o->{$attr} = $value;
return match ($key['cmd']) { return match ($key['cmd']) {
@ -407,7 +397,7 @@ class HomeController extends Controller
/** /**
* This is the main page render function * This is the main page render function
*/ */
public function home(Request $request) public function home(Request $request): \Illuminate\View\View
{ {
// Did we come here as a result of a redirect // Did we come here as a result of a redirect
return count(old()) return count(old())
@ -421,11 +411,11 @@ class HomeController extends Controller
* *
* @param ImportRequest $request * @param ImportRequest $request
* @param string $type * @param string $type
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application * @return \Illuminate\View\View
* @throws GeneralException * @throws GeneralException
* @throws VersionException * @throws VersionException
*/ */
public function import(ImportRequest $request,string $type) public function import(ImportRequest $request,string $type): \Illuminate\View\View
{ {
switch ($type) { switch ($type) {
case 'ldif': case 'ldif':
@ -453,22 +443,6 @@ class HomeController extends Controller
->with('ldif',htmlspecialchars($x)); ->with('ldif',htmlspecialchars($x));
} }
public function import_frame()
{
return view('frames.import');
}
/**
* LDAP Server INFO
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function info()
{
return view('frames.info')
->with('s',config('server'));
}
/** /**
* For any incoming request, work out the command and DN involved * For any incoming request, work out the command and DN involved
* *
@ -507,10 +481,10 @@ class HomeController extends Controller
* *
* @note Our route will validate that types are valid. * @note Our route will validate that types are valid.
* @param Request $request * @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View * @return \Illuminate\View\View
* @throws InvalidUsage * @throws InvalidUsage
*/ */
public function schema_frame(Request $request) public function schema_frame(Request $request): \Illuminate\View\View
{ {
// If an invalid key, we'll 404 // If an invalid key, we'll 404
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key))) if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
@ -536,9 +510,9 @@ class HomeController extends Controller
* Return the image for the logged in user or anonymous * Return the image for the logged in user or anonymous
* *
* @param Request $request * @param Request $request
* @return mixed * @return \Illuminate\Http\Response
*/ */
public function user_image(Request $request) public function user_image(Request $request): \Illuminate\Http\Response
{ {
$image = NULL; $image = NULL;
$content = NULL; $content = NULL;

View File

@ -25,6 +25,7 @@ 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); view()->share('user', auth()->user() ?: new User);
return $next($request); return $next($request);

View File

@ -82,22 +82,22 @@ class Entry extends Model
/** /**
* As attribute values are updated, or new ones created, we need to mirror that * As attribute values are updated, or new ones created, we need to mirror that
* into our $objects * into our $objects. This is called when we $o->key = $value
* *
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* @return $this * @return $this
*/ */
public function setAttribute(string $key, mixed $value): static public function setAttribute(string $key,mixed $value): static
{ {
parent::setAttribute($key,$value); parent::setAttribute($key,$value);
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
if ((! $this->objects->get($key)) && $value) $o = $this->objects->get($key) ?: Factory::create($this->dn ?: '',$key,[],Arr::get($this->attributes,'objectclass',[]));
$this->objects->put($key,Factory::create($key,[])); $o->values = collect($this->attributes[$key]);
$this->objects->get($key)->value = $this->attributes[$key]; $this->objects->put($key,$o);
return $this; return $this;
} }
@ -140,7 +140,18 @@ class Entry extends Model
/* METHODS */ /* METHODS */
public function addAttribute(string $key,mixed $value): void /**
* Add an attribute to this entry, if the attribute already exists, then we'll add the value to the existing item.
*
* This is primarily used by LDIF imports, where attributes have multiple entries over multiple lines
*
* @param string $key
* @param mixed $value
* @return void
* @throws AttributeException
* @note Attributes added this way dont have objectclass information, and the Model::attributes are not populated
*/
public function addAttributeItem(string $key,mixed $value): void
{ {
// While $value is mixed, it can only be a string // While $value is mixed, it can only be a string
if (! is_string($value)) if (! is_string($value))
@ -151,12 +162,10 @@ class Entry extends Model
if (! config('server')->schema('attributetypes')->has($key)) if (! config('server')->schema('attributetypes')->has($key))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key)); throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
if ($x=$this->objects->get($key)) { $o = $this->objects->get($key) ?: Attribute\Factory::create($this->dn ?: '',$key,[]);
$x->addValue($value); $o->addValue($value);
} else { $this->objects->put($key,$o);
$this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($value)));
}
} }
/** /**
@ -164,31 +173,30 @@ class Entry extends Model
* *
* @return Collection * @return Collection
*/ */
public function getAttributesAsObjects(): Collection private function getAttributesAsObjects(): Collection
{ {
$result = collect(); $result = collect();
foreach ($this->attributes as $attribute => $value) { foreach ($this->attributes as $attribute => $values) {
// If the attribute name has language tags // If the attribute name has tags
$matches = []; $matches = [];
if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) { if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
$attribute = $matches[1]; $attribute = $matches[1];
// If the attribute doesnt exist we'll create it // If the attribute doesnt exist we'll create it
$o = Arr::get($result,$attribute,Factory::create($attribute,[])); $o = Arr::get($result,$attribute,Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),Arr::get($this->original,'objectclass',[])));
$o->setLangTag($matches[3],$value); $o->setLangTag($matches[3],$values);
} else { } else {
$o = Factory::create($attribute,$value); $o = Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),Arr::get($this->original,'objectclass',[]));
} }
if (! $result->has($attribute)) { if (! $result->has($attribute)) {
// Set the rdn flag // Set the rdn flag
if (preg_match('/^'.$attribute.'=/i',$this->dn)) $o->is_rdn = preg_match('/^'.$attribute.'=/i',$this->dn);
$o->setRDN();
// Store our original value to know if this attribute has changed // Store our new values to know if this attribute has changed
$o->oldValues(Arr::get($this->original,$attribute,[])); $o->values = collect($values);
$result->put($attribute,$o); $result->put($attribute,$o);
} }
@ -297,7 +305,7 @@ class Entry extends Model
private function getRDNObject(): Attribute\RDN private function getRDNObject(): Attribute\RDN
{ {
$o = new Attribute\RDN('dn',['']); $o = new Attribute\RDN('','dn',['']);
// @todo for an existing object, return the base. // @todo for an existing object, return the base.
$o->setBase($this->rdnbase); $o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required)); $o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));

View File

@ -11,8 +11,8 @@ trait MD5Updates
{ {
public function isDirty(): bool public function isDirty(): bool
{ {
foreach ($this->values->diff($this->oldValues) as $key => $value) foreach ($this->values->diff($this->values_old) as $key => $value)
if (md5(Arr::get($this->oldValues,$key)) !== $value) if (md5(Arr::get($this->values_old,$key)) !== $value)
return TRUE; return TRUE;
return FALSE; return FALSE;

View File

@ -1 +1 @@
v2.0.2-rel v2.0.3-dev

View File

@ -22,7 +22,7 @@
<div class="h5 modal-title text-center"> <div class="h5 modal-title text-center">
<h4 class="mt-2"> <h4 class="mt-2">
<div class="app-logo mx-auto mb-3"><img class="w-75" src="{{ url('images/logo-h-lg.png') }}"></div> <div class="app-logo mx-auto mb-3"><img class="w-75" src="{{ url('images/logo-h-lg.png') }}"></div>
<small>@lang('Sign in to') <strong>{{ config('server')->name }}</strong></small> <small>@lang('Sign in to') <strong>{{ $server->name }}</strong></small>
</h4> </h4>
</div> </div>

View File

@ -32,7 +32,7 @@
<div class="scrollbar-sidebar"> <div class="scrollbar-sidebar">
<div class="app-sidebar__inner"> <div class="app-sidebar__inner">
<ul class="vertical-nav-menu"> <ul class="vertical-nav-menu">
<li class="app-sidebar__heading">{{ config('server')->name }}</li> <li class="app-sidebar__heading">{{ $server->name }}</li>
<li> <li>
<i id="treeicon" class="metismenu-icon fa-fw fas fa-sitemap"></i> <i id="treeicon" class="metismenu-icon fa-fw fas fa-sitemap"></i>
<span class="f16" id="tree"></span> <span class="f16" id="tree"></span>

View File

@ -2,7 +2,7 @@
<!-- $o=Binary\JpegPhoto::class --> <!-- $o=Binary\JpegPhoto::class -->
<x-attribute.layout :edit="$edit" :new="false" :o="$o"> <x-attribute.layout :edit="$edit" :new="false" :o="$o">
<table class="table table-borderless p-0 m-0"> <table class="table table-borderless p-0 m-0">
@foreach (($old ? $o->old_values : $o->values) as $value) @foreach (($old ? $o->values_old : $o->values) as $value)
<tr> <tr>
@switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE)) @switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@case('image/jpeg') @case('image/jpeg')

View File

@ -1,4 +1,4 @@
<!-- $o=Internal\Timestamp::class --> <!-- $o=Internal::class -->
@foreach (old($o->name_lc,$o->values) as $value) @foreach (old($o->name_lc,$o->values) as $value)
@if($loop->index)<br>@endif @if($loop->index)<br>@endif
{{ $value }} {{ $value }}

View File

@ -1,6 +1,6 @@
<div class="row pt-2"> <div class="row pt-2">
<div @class(['col-1','d-none'=>(! $edit) && (! ($detail ?? true))])></div> <div @class(['col-1','d-none'=>(! $edit) && (! ($detail ?? false))])></div>
<div class="col-10 p-2"> <div class="col-10">
<attribute id="{{ $o->name }}"> <attribute id="{{ $o->name }}">
{{ $slot }} {{ $slot }}
</attribute> </attribute>

View File

@ -1,6 +1,6 @@
<!-- $o=RDN::class --> <!-- $o=RDN::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o"> <x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value) @foreach(($o->values->count() ? $o->values : ['']) as $value)
@if($edit) @if($edit)
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<select class="form-select @error('rdn')is-invalid @enderror" id="rdn" name="rdn"> <select class="form-select @error('rdn')is-invalid @enderror" id="rdn" name="rdn">
@ -38,23 +38,23 @@
rdn_attr = $('select#rdn').val(); rdn_attr = $('select#rdn').val();
if (rdn_attr) { if (rdn_attr) {
$('#'+rdn_attr).find('input').attr('readonly',true); $('#'+rdn_attr).find('input').first().attr('readonly',true);
set_rdn_value(); set_rdn_value();
} }
function set_rdn_value() { function set_rdn_value() {
if (rdn_attr && rdn_value_set) if (rdn_attr && rdn_value_set)
$('#'+rdn_attr).find('input').val($('input#rdn_value').val()); $('#'+rdn_attr).find('input').first().val($('input#rdn_value').val());
} }
$('select#rdn').on('change',function() { $('select#rdn').on('change',function() {
// if rdn_attr is already set (and its now different), remove read only and clear value // if rdn_attr is already set (and its now different), remove read only and clear value
if (rdn_attr) if (rdn_attr)
$('#'+rdn_attr).find('input').attr('readonly',false).val(''); $('#'+rdn_attr).find('input').first().attr('readonly',false).val('');
// set RDN attribute read-only // set RDN attribute read-only
if (rdn_attr = $(this).val()) if (rdn_attr = $(this).val())
$('#'+rdn_attr).find('input').attr('readonly',true).val(''); $('#'+rdn_attr).find('input').first().attr('readonly',true).val('');
set_rdn_value(); set_rdn_value();
}) })

View File

@ -4,16 +4,16 @@
<span class="p-0 m-0"> <span class="p-0 m-0">
@if($o->is_rdn) @if($o->is_rdn)
<br/> <br/>
<button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button> <span class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
@elseif($edit && $o->can_addvalues) @elseif($edit && $o->can_addvalues)
@switch(get_class($o)) @switch(get_class($o))
@case(JpegPhoto::class) @case(JpegPhoto::class)
<button @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}" disabled><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</button> <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}" disabled><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span>
@break @break
@case(ObjectClass::class) @case(ObjectClass::class)
<button type="button" @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</button> <span type="button" @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</span>
<!-- NEW OBJECT CLASS --> <!-- NEW OBJECT CLASS -->
<div class="modal fade" id="new_objectclass-modal" tabindex="-1" aria-labelledby="new_objectclass-label" aria-hidden="true" data-bs-backdrop="static"> <div class="modal fade" id="new_objectclass-modal" tabindex="-1" aria-labelledby="new_objectclass-label" aria-hidden="true" data-bs-backdrop="static">
@ -220,7 +220,7 @@
<!-- All other attributes --> <!-- All other attributes -->
@default @default
@php($clone=TRUE) @php($clone=TRUE)
<button @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</button> <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
@section('page-scripts') @section('page-scripts')
@if($clone && $edit && $o->can_addvalues) @if($clone && $edit && $o->can_addvalues)

View File

@ -1,7 +1,7 @@
@extends('layouts.dn') @extends('layouts.dn')
@section('page_title') @section('page_title')
@include('fragment.dn.header',['o'=>($oo=config('server')->fetch(old('container',$container)))]) @include('fragment.dn.header',['o'=>($oo=$server->fetch(old('container',$container)))])
@endsection @endsection
@section('main-content') @section('main-content')
@ -30,7 +30,7 @@
id="objectclass" id="objectclass"
name="objectclass[]" name="objectclass[]"
:label="__('Select a Structural ObjectClass...')" :label="__('Select a Structural ObjectClass...')"
:options="($oc=config('server')->schema('objectclasses')) :options="($oc=$server->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural()) ->filter(fn($item)=>$item->isStructural())
->sortBy(fn($item)=>$item->name_lc) ->sortBy(fn($item)=>$item->name_lc)
->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])" ->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"
@ -90,8 +90,8 @@
}) })
$('.row.d-none').removeClass('d-none'); $('.row.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none'); $('span.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none'); $('span.deletable.d-none').removeClass('d-none');
$('#newattr-select.d-none').removeClass('d-none'); $('#newattr-select.d-none').removeClass('d-none');
} }

View File

@ -1,7 +1,7 @@
@extends('layouts.dn') @extends('layouts.dn')
@section('page_title') @section('page_title')
@include('fragment.dn.header',['o'=>($o ?? $o=config('server')->fetch($dn))]) @include('fragment.dn.header',['o'=>($o ?? $o=$server->fetch($dn))])
@endsection @endsection
@section('main-content') @section('main-content')
@ -178,8 +178,8 @@
}) })
$('.row.d-none').removeClass('d-none'); $('.row.d-none').removeClass('d-none');
$('button.addable.d-none').removeClass('d-none'); $('span.addable.d-none').removeClass('d-none');
$('button.deletable.d-none').removeClass('d-none'); $('span.deletable.d-none').removeClass('d-none');
@if($o->getMissingAttributes()->count()) @if($o->getMissingAttributes()->count())
$('#newattr-select.d-none').removeClass('d-none'); $('#newattr-select.d-none').removeClass('d-none');

View File

@ -4,7 +4,7 @@
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-upload"></i></div></td> <td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-upload"></i></div></td>
<td class="top text-start align-text-top p-0 pt-2"><strong>@lang('LDIF Import')</strong><br><small>@lang('To Server') <strong>{{ config('server')->name }}</strong></small></td> <td class="top text-start align-text-top p-2"><strong>@lang('LDIF Import')</strong><br><small>@lang('To Server') <strong>{{ $server->name }}</strong></small></td>
</tr> </tr>
</table> </table>
@endsection @endsection

View File

@ -2,7 +2,7 @@
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-upload"></i></div></td> <td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-upload"></i></div></td>
<td class="top text-start align-text-top p-0 pt-2"><strong>@lang('LDIF Import Result')</strong><br><small>@lang('To Server') <strong>{{ config('server')->name }}</strong></small></td> <td class="top text-start align-text-top p-0 pt-2"><strong>@lang('LDIF Import Result')</strong><br><small>@lang('To Server') <strong>{{ $server->name }}</strong></small></td>
</tr> </tr>
</table> </table>
@endsection @endsection

View File

@ -4,7 +4,7 @@
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-info"></i></div></td> <td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-info"></i></div></td>
<td class="top text-end align-text-top p-0 pt-2"><strong>@lang('Server Info')</strong><br><small>{{ $s->rootDSE()->entryuuid[0] ?? '' }}</small></td> <td class="top text-end align-text-top p-2"><strong>@lang('Server Info')</strong><br><small>{{ $server->rootDSE()->entryuuid[0] ?? '' }}</small></td>
</tr> </tr>
</table> </table>
@endsection @endsection
@ -13,10 +13,10 @@
<div class="main-card mb-3 card"> <div class="main-card mb-3 card">
<div class="card-body"> <div class="card-body">
<table class="table"> <table class="table">
@foreach ($s->rootDSE()->getObjects() as $attribute => $ao) @foreach ($server->rootDSE()->getObjects() as $attribute => $ao)
<tr> <tr>
<th class="w-25"> <th class="w-25">
{!! ($x=$s->schema('attributetypes',$attribute)) {!! ($x=$server->schema('attributetypes',$attribute))
? sprintf('<a class="attributetype" id="strtolower(%s)" href="%s">%s</a>',$x->name_lc,url('schema/attributetypes',$x->name_lc),$x->name) ? sprintf('<a class="attributetype" id="strtolower(%s)" href="%s">%s</a>',$x->name_lc,url('schema/attributetypes',$x->name_lc),$x->name)
: $attribute !!} : $attribute !!}
</th> </th>

View File

@ -5,7 +5,7 @@
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td> <td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td>
<td class="top text-end align-text-top p-0 pt-2"><strong>{{ Server::schemaDN() }}</strong></td> <td class="top text-end align-text-top p-2"><strong>{{ Server::schemaDN() }}</strong></td>
</tr> </tr>
</table> </table>
@endsection @endsection

View File

@ -29,9 +29,9 @@
</thead> </thead>
<tbody> <tbody>
@foreach ($o->getAttributesAsObjects()->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->keys()->max(),$oo->old_values->keys()->max())+1}}"> <th rowspan="{{ $x=max($oo->values->keys()->max(),$oo->values_old->keys()->max())+1}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr> <abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th> </th>
@for($xx=0;$xx<$x;$xx++) @for($xx=0;$xx<$x;$xx++)

View File

@ -31,10 +31,10 @@ Route::get('logout',[LoginController::class,'logout']);
Route::controller(HomeController::class)->group(function() { Route::controller(HomeController::class)->group(function() {
Route::middleware(AllowAnonymous::class)->group(function() { Route::middleware(AllowAnonymous::class)->group(function() {
Route::get('/','home'); Route::get('/','home');
Route::get('info','info'); Route::view('info','frames.info');
Route::get('debug','debug'); Route::view('debug','debug');
Route::post('frame','frame'); Route::post('frame','frame');
Route::get('import','import_frame'); Route::view('import','frames.import');
Route::get('schema','schema_frame'); Route::get('schema','schema_frame');
Route::group(['prefix'=>'user'],function() { Route::group(['prefix'=>'user'],function() {