Compare commits

..

15 Commits

Author SHA1 Message Date
9b33a20cc4 Dont run CI/CD on master 2025-03-16 10:19:23 +11:00
649749f9c1 MD5Update attributes cannot handle validation failures with a redirect back to the form, so restore the old values for now 2025-03-16 10:13:03 +11:00
5d3b8609bb Added an entry with a binary certification to test environment, with example LDIF to implement #75 2025-03-16 10:13:03 +11:00
93640959db Add our request()->root() to our debug page, implement Entry::getSortKeyAttribute() 2025-03-16 10:13:03 +11:00
f667250b2c Some PHP 8.4 deprecration fixes regarding NULL assignment to cast values on class instantiation 2025-03-16 10:13:03 +11:00
4a84c25ac7 Add Attribute required by ObjectClasses in schema viewer,
Attribute is_rdn dynamically calculated,
Fix Required by Objectclasses when viewing a DN
2025-03-16 10:13:03 +11:00
8ab5b4f35c Move direct controller direct view calls to route/web, add global $server to use in views, negating the need to use config('server') 2025-03-16 10:13:03 +11:00
de2d139288 Some DN rendering fixes, so that our Server Info renders correctly (aligned values) 2025-03-16 10:13:03 +11:00
d326d3c308 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-16 10:13:03 +11:00
d3fc9c135f 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-16 10:13:03 +11:00
eb6e0b8d43 Include LDAP diagnostic error message when we have an LDAP error 2025-03-16 10:13:03 +11:00
b01f7d5baf Attribute cleanup and optimisation in preparation to support attribute tags, HomeController return casting 2025-03-16 10:13:03 +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
7debd9ff2b Node updates to address vulnerabilities in babel/helpers and axios. Framework update too. 2025-03-13 09:43:47 +11:00
44 changed files with 400 additions and 340 deletions

View File

@ -1,6 +1,10 @@
name: Create Docker Image
run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push]
on:
push:
branches:
- '*'
- '!master'
env:
DOCKER_HOST: tcp://127.0.0.1:2375
ASSETS: c2780a3

View File

@ -17,31 +17,28 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
protected string $name;
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
protected bool $is_internal = FALSE;
// Is this attribute the RDN?
protected bool $is_rdn = FALSE;
protected(set) bool $is_internal = FALSE;
// MIN/MAX number of values
protected int $min_values_count = 0;
protected int $max_values_count = 0;
protected(set) int $min_values_count = 0;
protected(set) int $max_values_count = 0;
// RFC3866 Language Tags
/* @deprecated use $values/$values_old when playing with language 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
protected Collection $oldValues;
protected(set) Collection $values_old;
// Current Values
public Collection $values;
// The objectclasses of the entry that has this attribute
protected(set) Collection $oc;
/*
# Has the attribute been modified
@ -94,12 +91,23 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
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 The objectclasses that the DN of this attribute has
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{
$this->dn = $dn;
$this->name = $name;
$this->values = collect($values);
$this->values_old = collect($values);
$this->values = collect();
$this->oc = collect($oc);
$this->lang_tags = collect();
$this->oldValues = collect($values);
$this->schema = (new Server)
->schema('attributetypes',$name);
@ -132,18 +140,14 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
'hints' => $this->hints(),
// Can this attribute be edited
'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,
// Objectclasses that required this attribute for an LDAP entry
'required' => $this->required(),
// Is this attribute an RDN attribute
'is_rdn' => $this->isRDN(),
// We prefer the name as per the schema if it exists
'name' => $this->schema ? $this->schema->{$key} : $this->{$key},
// Attribute name in lower case
'name_lc' => strtolower($this->name),
// Old Values
'old_values' => $this->oldValues,
// Attribute values
'values' => $this->values,
// Required by Object Classes
'required_by' => $this->schema?->required_by_object_classes ?: collect(),
// Used in Object Classes
@ -153,17 +157,6 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'value':
$this->values = collect($values);
break;
default:
}
}
public function __toString(): string
{
return $this->name;
@ -240,11 +233,8 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// If this attribute name is an alias for the schema attribute name
// @todo
// objectClasses requiring this attribute
// @todo limit this to this DNs objectclasses
// eg: $result->put('required','Required by objectClasses: a,b');
if ($this->required_by->count())
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required_by->join(',')));
if ($this->required()->count())
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', ')));
// This attribute has language tags
if ($this->lang_tags->count())
@ -260,13 +250,24 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/
public function isDirty(): bool
{
return ($this->oldValues->count() !== $this->values->count())
|| ($this->values->diff($this->oldValues)->count() !== 0);
return ($this->values_old->count() !== $this->values->count())
|| ($this->values->diff($this->values_old)->count() !== 0);
}
public function oldValues(array $array): void
/**
* Work out if this attribute is an RDN attribute
*
* @return bool
*/
public function isRDN(): bool
{
$this->oldValues = collect($array);
// If we dont have an DN, then we cant know
if (! $this->dn)
return FALSE;
$rdns = collect(explode('+',substr($this->dn,0,strpos($this->dn,','))));
return $rdns->filter(fn($item) => str_starts_with($item,$this->name.'='))->count() > 0;
}
/**
@ -292,7 +293,7 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
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
@ -300,20 +301,29 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
return Arr::get($this->values,$key);
}
/**
* Work out if this attribute is required by an objectClass the entry has
*
* @return Collection
*/
public function required(): Collection
{
// If we dont have any objectclasses then we cant know if it is required
return $this->oc->count()
? $this->oc->intersect($this->schema->required_by_object_classes->keys())->sort()
: collect();
}
/**
* If this attribute has RFC3866 Language Tags, this will enable those values to be captured
*
* @param string $tag
* @param array $value
* @return void
* @deprecated
*/
public function setLangTag(string $tag,array $value): void
{
$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
*/
class Binary extends Attribute
abstract class Binary extends Attribute
{
}

View File

@ -49,15 +49,17 @@ class Factory
/**
* Create the new Object for an attribute
*
* @param string $dn
* @param string $attribute
* @param array $values
* @param array $oc
* @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);
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
{
protected bool $is_internal = TRUE;
protected(set) bool $is_internal = TRUE;
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
{
$pw = Arr::get($this->oldValues,$key);
$pw = Arr::get($this->values_old,$key);
return $pw
? str_repeat('*',16)
: NULL;

View File

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

View File

@ -15,13 +15,21 @@ final class ObjectClass extends Attribute
// The schema ObjectClasses for this objectclass of a DN
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 The objectclasses that the DN of this attribute has
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{
parent::__construct($name,$values);
parent::__construct($dn,$name,$values,['top']);
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$this->values->contains($item->name));
->filter(fn($item)=>$this->values->merge($this->values_old)->unique()->contains($item->name));
}
public function __get(string $key): mixed

View File

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

View File

@ -12,7 +12,7 @@ final class SMD5 extends Base
return $source === $this->encode($compare,$this->salted_salt($source));
}
public function encode(string $password,string $salt=NULL): string
public function encode(string $password,?string $salt=NULL): string
{
if (is_null($salt))
$salt = hex2bin(random_salt(self::salt));

View File

@ -12,7 +12,7 @@ final class SSHA extends Base
return $source === $this->encode($compare,$this->salted_salt($source));
}
public function encode(string $password,string $salt=NULL): string
public function encode(string $password,?string $salt=NULL): string
{
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha1',self::salt,$salt));
}

View File

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

View File

@ -46,7 +46,7 @@ class LDIF extends Import
if (! $line) {
if (! is_null($o)) {
// 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()));
@ -125,7 +125,7 @@ class LDIF extends Import
Log::debug(sprintf('%s: Adding Attribute [%s] value [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
if ($value)
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
$o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
else
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
if ($action) {
// 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()));

View File

@ -320,11 +320,12 @@ final class AttributeType extends Base {
* that is the list of objectClasses which must have this attribute.
*
* @param string $name The name of the objectClass to add.
* @param bool $structural
*/
public function addRequiredByObjectClass(string $name): void
public function addRequiredByObjectClass(string $name,bool $structural): void
{
if (! $this->required_by_object_classes->contains($name))
$this->required_by_object_classes->push($name);
if (! $this->required_by_object_classes->has($name))
$this->required_by_object_classes->put($name,$structural);
}
/**
@ -332,6 +333,7 @@ final class AttributeType extends Base {
* that is the list of objectClasses which provide this attribute.
*
* @param string $name The name of the objectClass to add.
* @param bool $structural
*/
public function addUsedInObjectClass(string $name,bool $structural): void
{
@ -544,7 +546,7 @@ final class AttributeType extends Base {
*/
public function validation(array $array): ?array
{
// For each item in array, we need to get the OC heirachy
// For each item in array, we need to get the OC hierarchy
$heirachy = collect($array)
->filter()
->map(fn($item)=>config('server')

View File

@ -428,7 +428,7 @@ final class Server
// Add Required By.
foreach ($must_attrs as $attr_name)
if ($this->attributetypes->has(strtolower($attr_name)))
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name);
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name,$object_class->isStructural());
// Force May
foreach ($object_class->getForceMayAttrs() as $attr_name)

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
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
*
* @param EntryAddRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @return View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request)
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
{
if (! old('step',$request->validated('step')))
abort(404);
@ -72,7 +61,7 @@ class HomeController extends Controller
$o->objectclass = $x;
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->addAttribute($ao,'');
$o->{$ao->name} = '';
$o->setRDNBase($key['dn']);
}
@ -92,24 +81,24 @@ class HomeController extends Controller
*
* @param Request $request
* @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->index = 0;
$x = $request->noheader
? (string)view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($id,[]))
$dn = $request->dn ? Crypt::decrypt($request->dn) : '';
return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($dn,$id,[],$request->objectclasses))
->with('value',$request->value)
->with('loop',$xx)
: (new AttributeType(Factory::create($id,[]),TRUE,collect($request->oc ?: [])))->render();
return $x;
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),TRUE)->render();
}
public function entry_create(EntryAddRequest $request)
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
{
$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
} 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::back()
->withInput()
->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
}
return Redirect::to('/')
->withFragment($o->getDNSecure());
}
public function entry_delete(Request $request)
public function entry_delete(Request $request): \Illuminate\Http\RedirectResponse
{
$dn = Crypt::decryptString($request->dn);
@ -196,7 +182,7 @@ class HomeController extends Controller
->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);
@ -215,12 +201,13 @@ class HomeController extends Controller
/**
* Render an available list of objectclasses for an Entry
*
* @param string $id
* @return mixed
* @param Request $request
* @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
->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);
$o = config('server')->fetch($dn);
@ -265,10 +252,10 @@ class HomeController extends Controller
* Show a confirmation to update a DN
*
* @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
*/
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);
@ -277,6 +264,8 @@ class HomeController extends Controller
foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value)
$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
if ($request->userpassword) {
$passwords = [];
@ -312,8 +301,10 @@ class HomeController extends Controller
* @param EntryRequest $request
* @return \Illuminate\Http\RedirectResponse
* @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);
@ -344,17 +335,14 @@ class HomeController extends Controller
}
} 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('/')
->withInput()
->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
}
return Redirect::to('/')
@ -368,9 +356,9 @@ class HomeController extends Controller
*
* @param Request $request
* @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 (($request->root().'/' !== url()->previous()) && $request->method() === 'POST')
@ -385,7 +373,9 @@ class HomeController extends Controller
// If we are rendering a DN, rebuild our object
$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;
return match ($key['cmd']) {
@ -407,7 +397,7 @@ class HomeController extends Controller
/**
* 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
return count(old())
@ -421,11 +411,11 @@ class HomeController extends Controller
*
* @param ImportRequest $request
* @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 VersionException
*/
public function import(ImportRequest $request,string $type)
public function import(ImportRequest $request,string $type): \Illuminate\View\View
{
switch ($type) {
case 'ldif':
@ -453,22 +443,6 @@ class HomeController extends Controller
->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
*
@ -507,10 +481,10 @@ class HomeController extends Controller
*
* @note Our route will validate that types are valid.
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @return \Illuminate\View\View
* @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 ($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
*
* @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;
$content = NULL;

View File

@ -25,6 +25,7 @@ class ApplicationSession
{
Config::set('server',new Server);
view()->share('server', Config::get('server'));
view()->share('user', auth()->user() ?: new User);
return $next($request);

View File

@ -2,13 +2,14 @@
namespace App\Ldap;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Connection as ConnectionBase;
use LdapRecord\LdapInterface;
class Connection extends ConnectionBase
{
public function __construct($config = [], LdapInterface $ldap = null)
public function __construct(DomainConfiguration|array $config=[],?LdapInterface $ldap=NULL)
{
parent::__construct($config,$ldap);

View File

@ -16,7 +16,9 @@ use App\Exceptions\InvalidUsage;
class Entry extends Model
{
// Our Attribute objects
private Collection $objects;
/* @deprecated */
private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in
private string $rdnbase;
@ -82,22 +84,22 @@ class Entry extends Model
/**
* 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 mixed $value
* @return $this
*/
public function setAttribute(string $key, mixed $value): static
public function setAttribute(string $key,mixed $value): static
{
parent::setAttribute($key,$value);
$key = $this->normalizeAttributeKey($key);
if ((! $this->objects->get($key)) && $value)
$this->objects->put($key,Factory::create($key,[]));
$o = $this->objects->get($key) ?: Factory::create($this->dn ?: '',$key,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($this->attributes[$key]);
$this->objects->get($key)->value = $this->attributes[$key];
$this->objects->put($key,$o);
return $this;
}
@ -131,16 +133,26 @@ class Entry extends Model
* Return a key to use for sorting
*
* @return string
* @todo This should be the DN in reverse order
*/
public function getSortKeyAttribute(): string
{
return $this->getDn();
return collect(explode(',',$this->getDn()))->reverse()->join(',');
}
/* 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
if (! is_string($value))
@ -151,12 +163,10 @@ class Entry extends Model
if (! config('server')->schema('attributetypes')->has($key))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
if ($x=$this->objects->get($key)) {
$x->addValue($value);
$o = $this->objects->get($key) ?: Attribute\Factory::create($this->dn ?: '',$key,[]);
$o->addValue($value);
} else {
$this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($value)));
}
$this->objects->put($key,$o);
}
/**
@ -164,31 +174,36 @@ class Entry extends Model
*
* @return Collection
*/
public function getAttributesAsObjects(): Collection
private function getAttributesAsObjects(): Collection
{
$result = collect();
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attribute => $value) {
// If the attribute name has language tags
foreach ($this->attributes as $attribute => $values) {
// If the attribute name has tags
$matches = [];
if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
$attribute = $matches[1];
// If the attribute doesnt exist we'll create it
$o = Arr::get($result,$attribute,Factory::create($attribute,[]));
$o->setLangTag($matches[3],$value);
$o = Arr::get(
$result,
$attribute,
Factory::create(
$this->dn,
$attribute,
Arr::get($this->original,$attribute,[]),
$entry_oc,
));
$o->setLangTag($matches[3],$values);
} else {
$o = Factory::create($attribute,$value);
$o = Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),$entry_oc);
}
if (! $result->has($attribute)) {
// Set the rdn flag
if (preg_match('/^'.$attribute.'=/i',$this->dn))
$o->setRDN();
// Store our original value to know if this attribute has changed
$o->oldValues(Arr::get($this->original,$attribute,[]));
// Store our new values to know if this attribute has changed
$o->values = collect($values);
$result->put($attribute,$o);
}
@ -297,8 +312,8 @@ class Entry extends Model
private function getRDNObject(): Attribute\RDN
{
$o = new Attribute\RDN('dn',['']);
// @todo for an existing object, return the base.
$o = new Attribute\RDN('','dn',['']);
// @todo for an existing object, rdnbase would be null, so dynamically get it from the DN.
$o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
@ -422,6 +437,7 @@ class Entry extends Model
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*
* @return $this
* @deprecated
*/
public function noObjectAttributes(): static
{

View File

@ -14,7 +14,7 @@ use LdapRecord\Models\Model as LdapRecord;
*/
class LoginObjectclassRule implements Rule
{
public function passes(LdapRecord $user, Eloquent $model = null): bool
public function passes(LdapRecord $user,?Eloquent $model=NULL): bool
{
if ($x=config('pla.login.objectclass')) {
return count(array_intersect($user->objectclass,$x));

View File

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

View File

@ -11,17 +11,15 @@ use App\Classes\LDAP\Attribute as LDAPAttribute;
class AttributeType extends Component
{
public Collection $oc;
public LDAPAttribute $o;
public bool $new;
private LDAPAttribute $o;
private bool $new;
/**
* Create a new component instance.
*/
public function __construct(LDAPAttribute $o,bool $new=FALSE,?Collection $oc=NULL)
public function __construct(LDAPAttribute $o,bool $new=FALSE)
{
$this->o = $o;
$this->oc = $oc;
$this->new = $new;
}
@ -32,7 +30,6 @@ class AttributeType extends Component
{
return view('components.attribute-type')
->with('o',$this->o)
->with('oc',$this->oc)
->with('new',$this->new);
}
}

38
composer.lock generated
View File

@ -1199,16 +1199,16 @@
},
{
"name": "laravel/framework",
"version": "v11.44.1",
"version": "v11.44.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d"
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"shasum": ""
},
"require": {
@ -1410,7 +1410,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-03-05T15:34:10+00:00"
"time": "2025-03-12T14:34:30+00:00"
},
{
"name": "laravel/prompts",
@ -6939,16 +6939,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.11",
"version": "11.5.12",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "3946ac38410be7440186c6e74584f31b15107fc7"
"reference": "d42785840519401ed2113292263795eb4c0f95da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3946ac38410be7440186c6e74584f31b15107fc7",
"reference": "3946ac38410be7440186c6e74584f31b15107fc7",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d42785840519401ed2113292263795eb4c0f95da",
"reference": "d42785840519401ed2113292263795eb4c0f95da",
"shasum": ""
},
"require": {
@ -6969,7 +6969,7 @@
"phpunit/php-timer": "^7.0.1",
"sebastian/cli-parser": "^3.0.2",
"sebastian/code-unit": "^3.0.2",
"sebastian/comparator": "^6.3.0",
"sebastian/comparator": "^6.3.1",
"sebastian/diff": "^6.0.2",
"sebastian/environment": "^7.2.0",
"sebastian/exporter": "^6.3.0",
@ -7020,7 +7020,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.11"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.12"
},
"funding": [
{
@ -7036,7 +7036,7 @@
"type": "tidelift"
}
],
"time": "2025-03-05T07:36:02+00:00"
"time": "2025-03-07T07:31:03+00:00"
},
{
"name": "sebastian/cli-parser",
@ -7210,16 +7210,16 @@
},
{
"name": "sebastian/comparator",
"version": "6.3.0",
"version": "6.3.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115"
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
"reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"shasum": ""
},
"require": {
@ -7238,7 +7238,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.2-dev"
"dev-main": "6.3-dev"
}
},
"autoload": {
@ -7278,7 +7278,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0"
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1"
},
"funding": [
{
@ -7286,7 +7286,7 @@
"type": "github"
}
],
"time": "2025-01-06T10:28:19+00:00"
"time": "2025-03-07T06:57:01+00:00"
},
{
"name": "sebastian/complexity",

145
package-lock.json generated
View File

@ -62,21 +62,21 @@
}
},
"node_modules/@babel/core": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.9",
"@babel/generator": "^7.26.10",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-module-transforms": "^7.26.0",
"@babel/helpers": "^7.26.9",
"@babel/parser": "^7.26.9",
"@babel/helpers": "^7.26.10",
"@babel/parser": "^7.26.10",
"@babel/template": "^7.26.9",
"@babel/traverse": "^7.26.9",
"@babel/types": "^7.26.9",
"@babel/traverse": "^7.26.10",
"@babel/types": "^7.26.10",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -101,13 +101,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
"integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
"integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9",
"@babel/parser": "^7.26.10",
"@babel/types": "^7.26.10",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@ -378,25 +378,25 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/types": "^7.26.10"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.9"
"@babel/types": "^7.26.10"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -1235,15 +1235,15 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.9.tgz",
"integrity": "sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz",
"integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.25.9",
"@babel/helper-plugin-utils": "^7.26.5",
"babel-plugin-polyfill-corejs2": "^0.4.10",
"babel-plugin-polyfill-corejs3": "^0.10.6",
"babel-plugin-polyfill-corejs3": "^0.11.0",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"semver": "^6.3.1"
},
@ -1485,19 +1485,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
"integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.3",
"core-js-compat": "^3.40.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -1522,9 +1509,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -1548,16 +1535,16 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
"integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
"integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.9",
"@babel/parser": "^7.26.9",
"@babel/generator": "^7.26.10",
"@babel/parser": "^7.26.10",
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9",
"@babel/types": "^7.26.10",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1566,9 +1553,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@ -2261,9 +2248,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@ -2780,9 +2767,9 @@
"license": "MIT"
},
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"funding": [
{
"type": "opencollective",
@ -2799,11 +2786,11 @@
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001646",
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.1",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
@ -2817,9 +2804,9 @@
}
},
"node_modules/axios": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -2870,13 +2857,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
"integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
"integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.2",
"core-js-compat": "^3.38.0"
"@babel/helper-define-polyfill-provider": "^0.6.3",
"core-js-compat": "^3.40.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@ -3328,9 +3315,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001702",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz",
"integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==",
"version": "1.0.30001703",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz",
"integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==",
"funding": [
{
"type": "opencollective",
@ -4341,9 +4328,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.112",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz",
"integrity": "sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==",
"version": "1.5.115",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.115.tgz",
"integrity": "sha512-MN1nahVHAQMOz6dz6bNZ7apgqc9InZy7Ja4DBEVCTdeiUcegbyOYE9bi/f2Z/z6ZxLi0RxLpyJ3EGe+4h3w73A==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -6562,9 +6549,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz",
"integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==",
"funding": [
{
"type": "github",
@ -9044,9 +9031,9 @@
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.3.13",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.13.tgz",
"integrity": "sha512-JG3pBixF6kx2o0Yfz2K6pqh72DpwTI08nooHd06tcj5WyIt5SsSiUYqRT+kemrGUNSuSzVhwfZ28aO8gogajNQ==",
"version": "5.3.14",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",

View File

@ -22,7 +22,7 @@
<div class="h5 modal-title text-center">
<h4 class="mt-2">
<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>
</div>

View File

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

View File

@ -2,7 +2,7 @@
<!-- $o=Binary\JpegPhoto::class -->
<x-attribute.layout :edit="$edit" :new="false" :o="$o">
<table class="table table-borderless p-0 m-0">
@foreach (($old ? $o->old_values : $o->values) as $value)
@foreach ($o->values_old as $value)
<tr>
@switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@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)
@if($loop->index)<br>@endif
{{ $value }}

View File

@ -1,6 +1,7 @@
<!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=Password::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value)
@foreach($o->values_old as $value)
@if($edit)
<div class="input-group has-validation mb-3">
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ md5($value) }}" @readonly(true)>

View File

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

View File

@ -1,6 +1,7 @@
<!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=Password::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value)
@foreach($o->values_old as $value)
@if($edit)
<div class="input-group has-validation mb-3">
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/>

View File

@ -1,6 +1,6 @@
<!-- $o=RDN::class -->
<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)
<div class="input-group has-validation mb-3">
<select class="form-select @error('rdn')is-invalid @enderror" id="rdn" name="rdn">
@ -38,23 +38,23 @@
rdn_attr = $('select#rdn').val();
if (rdn_attr) {
$('#'+rdn_attr).find('input').attr('readonly',true);
$('#'+rdn_attr).find('input').first().attr('readonly',true);
set_rdn_value();
}
function set_rdn_value() {
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() {
// if rdn_attr is already set (and its now different), remove read only and clear value
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
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();
})

View File

@ -4,16 +4,16 @@
<span class="p-0 m-0">
@if($o->is_rdn)
<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)
@switch(get_class($o))
@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
@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 -->
<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 -->
@default
@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')
@if($clone && $edit && $o->can_addvalues)

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach(\App\Classes\LDAP\Server::baseDNs()->sort(function($item) { return $item->sortKey; }) as $item)
@foreach($server->baseDNs()->sort(fn($item)=>$item->sort_key) as $item)
<tr>
<td class="ps-0">{{ $item->getDn() }}</td>
</tr>
@ -36,7 +36,13 @@
<!-- Schema DN -->
<tr>
<td>Schema DN</td>
<td>{{ \App\Classes\LDAP\Server::schemaDN() }}</td>
<td>{{ $server->schemaDN() }}</td>
</tr>
<!-- Schema DN -->
<tr>
<td>Root URL</td>
<td>{{ request()->root() }}</td>
</tr>
</tbody>
</table>

View File

@ -106,6 +106,24 @@
@endif
</td>
</tr>
<tr>
<td>@lang('Required by ObjectClasses')</td>
<td>
@if ($o->required_by_object_classes->count())
@foreach ($o->required_by_object_classes as $class => $structural)
@if($structural)
<strong>
@endif
<a class="objectclass" id="{{ strtolower($class) }}" href="#{{ strtolower($class) }}">{{ $class }}</a>
@if($structural)
</strong>
@endif
@endforeach
@else
@lang('(none)')
@endif
</td>
</tr>
<tr>
<td>@lang('Force as MAY by config')</td><td><strong>@lang($o->forced_as_may ? 'Yes' : 'No')</strong></td>
</tr>

View File

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

View File

@ -1,7 +1,7 @@
@extends('layouts.dn')
@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
@section('main-content')
@ -178,8 +178,8 @@
})
$('.row.d-none').removeClass('d-none');
$('button.addable.d-none').removeClass('d-none');
$('button.deletable.d-none').removeClass('d-none');
$('span.addable.d-none').removeClass('d-none');
$('span.deletable.d-none').removeClass('d-none');
@if($o->getMissingAttributes()->count())
$('#newattr-select.d-none').removeClass('d-none');

View File

@ -4,7 +4,7 @@
<table class="table table-borderless">
<tr>
<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>
</table>
@endsection

View File

@ -2,7 +2,7 @@
<table class="table table-borderless">
<tr>
<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>
</table>
@endsection

View File

@ -4,7 +4,7 @@
<table class="table table-borderless">
<tr>
<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>
</table>
@endsection
@ -13,10 +13,10 @@
<div class="main-card mb-3 card">
<div class="card-body">
<table class="table">
@foreach ($s->rootDSE()->getObjects() as $attribute => $ao)
@foreach ($server->rootDSE()->getObjects() as $attribute => $ao)
<tr>
<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)
: $attribute !!}
</th>

View File

@ -5,7 +5,7 @@
<table class="table table-borderless">
<tr>
<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>
</table>
@endsection

View File

@ -29,9 +29,9 @@
</thead>
<tbody>
@foreach ($o->getAttributesAsObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<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>
</th>
@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::middleware(AllowAnonymous::class)->group(function() {
Route::get('/','home');
Route::get('info','info');
Route::get('debug','debug');
Route::view('info','frames.info');
Route::view('debug','debug');
Route::post('frame','frame');
Route::get('import','import_frame');
Route::view('import','frames.import');
Route::get('schema','schema_frame');
Route::group(['prefix'=>'user'],function() {

View File

@ -0,0 +1,24 @@
dn: uid=usercert,dc=example.com
cn: cn
gidNumber: 1111
homeDirectory: /home
objectClass: account
objectClass: posixAccount
objectClass: pkiUser
uid: usercert
uidNumber: 111
usercertificate;binary:: MIIC2TCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQQFADBGMQswCQYD
VQQGEwJJVDENMAsGA1UEChMESU5GTjESMBAGA1UECxMJQXV0aG9yaXR5MRQwEgYDVQQDEwtJTkZO
IENBICgyKTAeFw05OTA2MjMxMTE2MDdaFw0wMzA4MDExMTE2MDdaMEYxCzAJBgNVBAYTAklUMQ0w
CwYDVQQKEwRJTkZOMRIwEAYDVQQLEwlBdXRob3JpdHkxFDASBgNVBAMTC0lORk4gQ0EgKDIpMIGf
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrHdRKJsobcjXz/OsGjyq8v73DbggG3JCGrQZ9f1Vm
9RrIWJPwggczqgxwWL6JLPKglxbUjAtUxiZm3fw2kX7FGMUq5JaN/Pk2PT4ExA7bYLnbLGZ9jKJs
Dh4bNOKrGRIxRO9Ff+YwmH8EQdoVpSRFbBpNnoDIkHLc4DtzB+B4wwIDAQABo4HWMIHTMAwGA1Ud
EwQFMAMBAf8wHQYDVR0OBBYEFK3QjOXGc4j9LqYEYTn9WvSRAcusMG4GA1UdIwRnMGWAFK3QjOXG
c4j9LqYEYTn9WvSRAcusoUqkSDBGMQswCQYDVQQGEwJJVDENMAsGA1UEChMESU5GTjESMBAGA1UE
CxMJQXV0aG9yaXR5MRQwEgYDVQQDEwtJTkZOIENBICgyKYIBADALBgNVHQ8EBAMCAQYwEQYJYIZI
AYb4QgEBBAQDAgAHMAkGA1UdEQQCMAAwCQYDVR0SBAIwADANBgkqhkiG9w0BAQQFAAOBgQCDs5b1
jmbIYVq2epd5iDjQ109SJ/V7b6DFw2NIl8CWeDPOOjL1E5M8dnlmCDeTR2TlBxqUZaBBJZPqzFdv
xpxqsHC0HfkCXAnUe5MaefFNAH9WbxoB/A2pkXtT6WGWed+QsL5wyKJaO4oD9UD5T+x12aGsHcsD
Cy3EVEaGEOl+/A==