Compare commits

..

No commits in common. "master" and "2.0.1" have entirely different histories.

107 changed files with 1157 additions and 2211 deletions

View File

@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push]
env:
DOCKER_HOST: tcp://127.0.0.1:2375
ASSETS: c2780a3
ASSETS: 509b1a1
jobs:
test:

View File

@ -10,9 +10,6 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is. (One issue per report please.)
**Version of PLA**
What version of PLA are you using. Are you using the docker container, an distribution package or running from GIT source?
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'

View File

@ -1 +0,0 @@
blank_issues_enabled: false

View File

@ -46,7 +46,6 @@ The update to v2 is progressing well - here is a list of work to do and done:
- [X] Delete extra values for Attributes that support multiple values
- [ ] Delete Attributes
- [ ] Templates to enable entries to conform to a custom standard
- [ ] Autopopulate attribute values
- [X] Login to LDAP server
- [X] Configure login by a specific attribute
- [X] Logout LDAP server
@ -54,14 +53,12 @@ The update to v2 is progressing well - here is a list of work to do and done:
- [X] Import LDIF
- [X] Schema Browser
- [ ] Searching
- [ ] Enforcing attribute uniqueness
- [ ] Is there something missing?
Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory
- [ ] 389 Directory Server
If there is an LDAP server that you have that you would like to have supported, please open an issue to request it.
You might need to provide access, provide a copy or instructions to get an environment for testing. If you have enabled

View File

@ -7,38 +7,41 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Schema\AttributeType;
use App\Ldap\Entry;
/**
* Represents an attribute of an LDAP Object
*/
class Attribute implements \Countable, \ArrayAccess
class Attribute implements \Countable, \ArrayAccess, \Iterator
{
// Attribute Name
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(set) bool $is_internal = FALSE;
protected(set) bool $no_attr_tags = FALSE;
protected bool $is_internal = FALSE;
// Is this attribute the RDN?
protected bool $is_rdn = FALSE;
// MIN/MAX number of values
protected(set) int $min_values_count = 0;
protected(set) int $max_values_count = 0;
protected int $min_values_count = 0;
protected int $max_values_count = 0;
// The schema's representation of this attribute
protected(set) ?AttributeType $schema;
// RFC3866 Language Tags
protected Collection $lang_tags;
// 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
private Collection $_values_old;
// Current Values
private Collection $_values;
// The objectclasses of the entry that has this attribute
protected(set) Collection $oc;
private const SYNTAX_CERTIFICATE = '1.3.6.1.4.1.1466.115.121.1.8';
private const SYNTAX_CERTIFICATE_LIST = '1.3.6.1.4.1.1466.115.121.1.9';
protected Collection $oldValues;
/*
# Has the attribute been modified
@ -91,22 +94,12 @@ class Attribute implements \Countable, \ArrayAccess
protected $postvalue = array();
*/
/**
* 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=[])
public function __construct(string $name,array $values)
{
$this->dn = $dn;
$this->name = $name;
$this->_values = collect($values);
$this->_values_old = collect($values);
$this->oc = collect($oc);
$this->values = collect($values);
$this->lang_tags = collect();
$this->oldValues = collect($values);
$this->schema = (new Server)
->schema('attributetypes',$name);
@ -126,11 +119,6 @@ class Attribute implements \Countable, \ArrayAccess
*/
}
public function __call(string $name,array $arguments)
{
abort(555,'Method not handled: '.$name);
}
public function __get(string $key): mixed
{
return match ($key) {
@ -144,22 +132,22 @@ class Attribute implements \Countable, \ArrayAccess
'hints' => $this->hints(),
// Can this attribute be edited
'is_editable' => $this->schema ? $this->schema->{$key} : NULL,
// Objectclasses that required this attribute for an LDAP entry
'required' => $this->required(),
// Is this attribute an RDN attribute
'is_rdn' => $this->isRDN(),
// 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
'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
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
// The current attribute values
'values' => $this->no_attr_tags ? $this->tagValues() : $this->_values,
// The original attribute values
'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old,
default => throw new \Exception('Unknown key:' . $key),
};
@ -168,16 +156,11 @@ class Attribute implements \Countable, \ArrayAccess
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
$this->_values = $values;
break;
case 'values_old':
$this->_values_old = $values;
case 'value':
$this->values = collect($values);
break;
default:
throw new \Exception('Unknown key:'.$key);
}
}
@ -186,21 +169,49 @@ class Attribute implements \Countable, \ArrayAccess
return $this->name;
}
/* INTERFACE */
public function addValue(string $value): void
{
$this->values->push($value);
}
public function current(): mixed
{
return $this->values->get($this->counter);
}
public function next(): void
{
$this->counter++;
}
public function key(): mixed
{
return $this->counter;
}
public function valid(): bool
{
return $this->values->has($this->counter);
}
public function rewind(): void
{
$this->counter = 0;
}
public function count(): int
{
return $this->_values->dot()->count();
return $this->values->count();
}
public function offsetExists(mixed $offset): bool
{
return $this->_values->dot()->has($offset);
return ! is_null($this->values->has($offset));
}
public function offsetGet(mixed $offset): mixed
{
return $this->_values->dot()->get($offset);
return $this->values->get($offset);
}
public function offsetSet(mixed $offset, mixed $value): void
@ -213,36 +224,15 @@ class Attribute implements \Countable, \ArrayAccess
// We cannot clear values using array syntax
}
/* METHODS */
public function addValue(string $tag,array $values): void
{
$this->_values->put(
$tag,
array_unique(array_merge($this->_values
->get($tag,[]),$values)));
}
public function addValueOld(string $tag,array $values): void
{
$this->_values_old->put(
$tag,
array_unique(array_merge($this->_values_old
->get($tag,[]),$values)));
}
/**
* Return the hints about this attribute, ie: RDN, Required, etc
*
* @return Collection
* @return array
*/
public function hints(): Collection
public function hints(): array
{
$result = collect();
if ($this->is_internal)
return $result;
// Is this Attribute an RDN
if ($this->is_rdn)
$result->put(__('rdn'),__('This attribute is required for the RDN'));
@ -250,14 +240,17 @@ class Attribute implements \Countable, \ArrayAccess
// If this attribute name is an alias for the schema attribute name
// @todo
if ($this->required()->count())
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', ')));
// 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 attribute is a dynamic attribute
if ($this->isDynamic())
$result->put(__('dynamic'),__('These are dynamic values present as a result of another attribute'));
// This attribute has language tags
if ($this->lang_tags->count())
$result->put(__('language tags'),sprintf('%s: %d',__('This Attribute has Language Tags'),$this->lang_tags->count()));
return $result;
return $result->toArray();
}
/**
@ -267,38 +260,13 @@ class Attribute implements \Countable, \ArrayAccess
*/
public function isDirty(): bool
{
return (($a=$this->values_old->dot()->filter())->keys()->count() !== ($b=$this->values->dot()->filter())->keys()->count())
|| ($a->count() !== $b->count())
|| ($a->diff($b)->count() !== 0);
return ($this->oldValues->count() !== $this->values->count())
|| ($this->values->diff($this->oldValues)->count() !== 0);
}
/**
* Are these values as a result of a dynamic attribute
*
* @return bool
*/
public function isDynamic(): bool
public function oldValues(array $array): void
{
return $this->schema->used_in_object_classes
->keys()
->intersect($this->schema->heirachy($this->oc))
->count() === 0;
}
/**
* Work out if this attribute is an RDN attribute
*
* @return bool
*/
public function isRDN(): bool
{
// 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;
$this->oldValues = collect($array);
}
/**
@ -311,61 +279,37 @@ class Attribute implements \Countable, \ArrayAccess
*/
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
$view = match ($this->schema->syntax_oid) {
self::SYNTAX_CERTIFICATE => view('components.syntax.certificate'),
self::SYNTAX_CERTIFICATE_LIST => view('components.syntax.certificatelist'),
default => view()->exists($x = 'components.attribute.' . $this->name_lc)
? view($x)
: view('components.attribute'),
};
return $view
return view('components.attribute')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string
public function render_item_old(int $key): ?string
{
return match ($this->schema->syntax_oid) {
self::SYNTAX_CERTIFICATE => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)),
self::SYNTAX_CERTIFICATE_LIST => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)),
default => Arr::get($this->values_old->dot(),$dotkey),
};
return Arr::get($this->old_values,$key);
}
public function render_item_new(string $dotkey): ?string
public function render_item_new(int $key): ?string
{
return Arr::get($this->values->dot(),$dotkey);
return Arr::get($this->values,$key);
}
/**
* Work out if this attribute is required by an objectClass the entry has
* If this attribute has RFC3866 Language Tags, this will enable those values to be captured
*
* @return Collection
* @param string $tag
* @param array $value
* @return void
*/
public function required(): Collection
public function setLangTag(string $tag,array $value): void
{
// If we dont have any objectclasses then we cant know if it is required
return $this->oc->count()
? $this->oc->intersect($this->required_by->keys())->sort()
: collect();
$this->lang_tags->put($tag,$value);
}
public function tagValues(string $tag=Entry::TAG_NOTAG): Collection
public function setRDN(): void
{
return collect($this->_values
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
}
public function tagValuesOld(string $tag=Entry::TAG_NOTAG): Collection
{
return collect($this->_values_old
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
$this->is_rdn = TRUE;
}
}

View File

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

View File

@ -5,7 +5,6 @@ namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@ -15,14 +14,13 @@ final class JpegPhoto extends Binary
{
use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('f',new \finfo);
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values is a binary user certificate
*/
final class Certificate extends Attribute
{
use MD5Updates;
private array $_object = [];
public function certificate(int $key=0): string
{
return sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----",
join("\n",str_split(base64_encode(Arr::get($this->values_old,'binary.'.$key)),80))
);
}
public function cert_info(string $index,int $key=0): mixed
{
if (! array_key_exists($key,$this->_object))
$this->_object[$key] = openssl_x509_parse(openssl_x509_read($this->certificate($key)));
return Arr::get($this->_object[$key],$index);
}
public function expires($key=0): Carbon
{
return Carbon::createFromTimestampUTC($this->cert_info('validTo_time_t',$key));
}
public function subject($key=0): string
{
$subject = collect($this->cert_info('subject',$key))->reverse();
return $subject->map(fn($item,$key)=>sprintf("%s=%s",$key,$item))->join(',');
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values is a binary user certificate
*/
final class CertificateList extends Attribute
{
use MD5Updates;
}

View File

@ -20,59 +20,39 @@ class Factory
* Map of attributes to appropriate class
*/
public const map = [
'authorityrevocationlist' => CertificateList::class,
'cacertificate' => Certificate::class,
'certificaterevocationlist' => CertificateList::class,
'createtimestamp' => Internal\Timestamp::class,
'creatorsname' => Internal\DN::class,
'configcontext' => Schema\Generic::class,
'contextcsn' => Internal\CSN::class,
'entrycsn' => Internal\CSN::class,
'entrydn' => Internal\DN::class,
'entryuuid' => Internal\UUID::class,
'etag' => Internal\Etag::class,
'krblastfailedauth' => Attribute\NoAttrTags\Generic::class,
'krblastpwdchange' => Attribute\NoAttrTags\Generic::class,
'krblastsuccessfulauth' => Attribute\NoAttrTags\Generic::class,
'krbpasswordexpiration' => Attribute\NoAttrTags\Generic::class,
'krbloginfailedcount' => Attribute\NoAttrTags\Generic::class,
'krbprincipalkey' => KrbPrincipalKey::class,
'krbticketflags' => KrbTicketFlags::class,
'gidnumber' => GidNumber::class,
'hassubordinates' => Internal\HasSubordinates::class,
'jpegphoto' => Binary\JpegPhoto::class,
'modifytimestamp' => Internal\Timestamp::class,
'modifiersname' => Internal\DN::class,
'monitorcontext' => Schema\Generic::class,
'namingcontexts' => Schema\Generic::class,
'numsubordinates' => Internal\NumSubordinates::class,
'objectclass' => ObjectClass::class,
'pwdpolicysubentry' => Internal\PwdPolicySubentry::class,
'structuralobjectclass' => Internal\StructuralObjectClass::class,
'subschemasubentry' => Internal\SubschemaSubentry::class,
'supportedcontrol' => Schema\OID::class,
'supportedextension' => Schema\OID::class,
'supportedfeatures' => Schema\OID::class,
'supportedldapversion' => Schema\Generic::class,
'supportedsaslmechanisms' => Schema\Mechanisms::class,
'usercertificate' => Certificate::class,
'userpassword' => Password::class,
];
/**
* 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 $dn,string $attribute,array $values,array $oc=[]): Attribute
public static function create(string $attribute,array $values): 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($dn,$attribute,$values,$oc);
return new $class($attribute,$values);
}
}

View File

@ -9,5 +9,4 @@ use App\Classes\LDAP\Attribute;
*/
final class GidNumber extends Attribute
{
protected(set) bool $no_attr_tags = FALSE;
}

View File

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

View File

@ -1,12 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an Etag Attribute
*/
final class Etag extends Internal
{
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an NumSubordinates Attribute
*/
final class NumSubordinates extends Internal
{
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an PwdPolicySubentry Attribute
*/
final class PwdPolicySubentry extends Internal
{
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values are passwords
*/
final class KrbPrincipalKey extends Attribute
{
use MD5Updates;
protected(set) bool $no_attr_tags = TRUE;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.krbprincipalkey')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string
{
return parent::render_item_old($dotkey)
? str_repeat('*',16)
: NULL;
}
public function render_item_new(string $dotkey): ?string
{
return parent::render_item_new($dotkey)
? str_repeat('*',16)
: NULL;
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag
* See RFC4120
*/
final class KrbTicketFlags extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
private const DISALLOW_POSTDATED = 0x00000001;
private const DISALLOW_FORWARDABLE = 0x00000002;
private const DISALLOW_TGT_BASED = 0x00000004;
private const DISALLOW_RENEWABLE = 0x00000008;
private const DISALLOW_PROXIABLE = 0x00000010;
private const DISALLOW_DUP_SKEY = 0x00000020;
private const DISALLOW_ALL_TIX = 0x00000040;
private const REQUIRES_PRE_AUTH = 0x00000080;
private const REQUIRES_HW_AUTH = 0x00000100;
private const REQUIRES_PWCHANGE = 0x00000200;
private const DISALLOW_SVR = 0x00001000;
private const PWCHANGE_SERVICE = 0x00002000;
private static function helpers(): Collection
{
$helpers = collect([
log(self::DISALLOW_POSTDATED,2) => __('KRB_DISALLOW_POSTDATED'),
log(self::DISALLOW_FORWARDABLE,2) => __('KRB_DISALLOW_FORWARDABLE'),
log(self::DISALLOW_TGT_BASED,2) => __('KRB_DISALLOW_TGT_BASED'),
log(self::DISALLOW_RENEWABLE,2) => __('KRB_DISALLOW_RENEWABLE'),
log(self::DISALLOW_PROXIABLE,2) => __('KRB_DISALLOW_PROXIABLE'),
log(self::DISALLOW_DUP_SKEY,2) => __('KRB_DISALLOW_DUP_SKEY'),
log(self::DISALLOW_ALL_TIX,2) => __('KRB_DISALLOW_ALL_TIX'),
log(self::REQUIRES_PRE_AUTH,2) => __('KRB_REQUIRES_PRE_AUTH'),
log(self::REQUIRES_HW_AUTH,2) => __('KRB_REQUIRES_HW_AUTH'),
log(self::REQUIRES_PWCHANGE,2) => __('KRB_REQUIRES_PWCHANGE'),
log(self::DISALLOW_SVR,2) => __('KRB_DISALLOW_SVR'),
log(self::PWCHANGE_SERVICE,2) => __('KRB_PWCHANGE_SERVICE'),
])
->replace(config('pla.krb.bits',[]));
return $helpers;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.krbticketflags')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('helper',static::helpers());
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\NoAttrTags;
use App\Classes\LDAP\Attribute;
/**
* Represents an Attribute that doesnt have Lang Tags
*/
class Generic extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
}

View File

@ -6,31 +6,22 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents an ObjectClass Attribute
*/
final class ObjectClass extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
// The schema ObjectClasses for this objectclass of a DN
protected Collection $oc_schema;
/**
* 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 (ignored for objectclasses)
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
public function __construct(string $name,array $values)
{
parent::__construct($dn,$name,$values,['top']);
parent::__construct($name,$values);
$this->set_oc_schema($this->tagValuesOld());
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$this->values->contains($item->name));
}
public function __get(string $key): mixed
@ -41,22 +32,6 @@ final class ObjectClass extends Attribute
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
parent::__set($key,$values);
// We need to populate oc_schema, if we are a new OC and thus dont have any old values
if (! $this->values_old->count() && $this->values->count())
$this->set_oc_schema($this->tagValues());
break;
default: parent::__set($key,$values);
}
}
/**
* Is a specific value the structural objectclass
*
@ -75,15 +50,7 @@ final class ObjectClass extends Attribute
return view('components.attribute.objectclass')
->with('o',$this)
->with('edit',$edit)
->with('langtag',Entry::TAG_NOTAG)
->with('old',$old)
->with('new',$new);
}
private function set_oc_schema(Collection $tv): void
{
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$tv->contains($item->name));
}
}

View File

@ -15,9 +15,6 @@ use App\Traits\MD5Updates;
final class Password extends Attribute
{
use MD5Updates;
protected(set) bool $no_attr_tags = TRUE;
private const password_helpers = 'Classes/LDAP/Attribute/Password';
public const commands = 'App\\Classes\\LDAP\\Attribute\\Password\\';
@ -88,23 +85,19 @@ final class Password extends Attribute
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
}
public function render_item_old(string $dotkey): ?string
public function render_item_old(int $key): ?string
{
$pw = parent::render_item_old($dotkey);
$pw = Arr::get($this->oldValues,$key);
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;
}
public function render_item_new(string $dotkey): ?string
public function render_item_new(int $key): ?string
{
$pw = parent::render_item_new($dotkey);
$pw = Arr::get($this->values,$key);
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;
}
}

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

@ -24,11 +24,11 @@ final class RDN extends Attribute
};
}
public function hints(): Collection
public function hints(): array
{
return collect([
return [
'required' => __('RDN is required')
]);
];
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View

View File

@ -14,7 +14,6 @@ use App\Classes\LDAP\Attribute;
abstract class Schema extends Attribute
{
protected bool $internal = TRUE;
protected(set) bool $no_attr_tags = TRUE;
protected static function _get(string $filename,string $string,string $key): ?string
{
@ -31,7 +30,7 @@ abstract class Schema extends Attribute
while (! feof($f)) {
$line = trim(fgets($f));
if ((! $line) || preg_match('/^#/',$line))
if (! $line OR preg_match('/^#/',$line))
continue;
$fields = explode(':',$line);
@ -42,15 +41,12 @@ abstract class Schema extends Attribute
'desc'=>Arr::get($fields,3,__('No description available, can you help with one?')),
]);
}
fclose($f);
return $result;
});
return Arr::get(($array ? $array->get($string) : []),
$key,
__('No description available, can you help with one?'));
return Arr::get(($array ? $array->get($string) : []),$key);
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View

View File

@ -1,20 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
/**
* Represents a Generic Schema Attribute
*/
class Generic extends Schema
{
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.generic')
->with('o',$this);
}
}

View File

@ -5,7 +5,6 @@ namespace App\Classes\LDAP\Export;
use Illuminate\Support\Str;
use App\Classes\LDAP\Export;
use App\Ldap\Entry;
/**
* Export from LDAP using an LDIF format
@ -42,7 +41,6 @@ class LDIF extends Export
// Display Attributes
foreach ($o->getObjects() as $ao) {
if ($ao->no_attr_tags)
foreach ($ao->values as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
@ -50,17 +48,6 @@ class LDIF extends Export
: sprintf('%s:: %s',$ao->name,base64_encode($value))
,$this->br);
}
else
foreach ($ao->values as $tag => $tagvalues) {
foreach ($tagvalues as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
? sprintf('%s: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),$value)
: sprintf('%s:: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),base64_encode($value))
,$this->br);
}
}
}
}

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,6 +48,7 @@ abstract class Import
* @param int $action
* @return Collection
* @throws GeneralException
* @throws ObjectExistsException
*/
final protected function commit(Entry $o,int $action): Collection
{
@ -56,8 +57,7 @@ abstract class Import
try {
$o->save();
} catch (LdapRecordException $e) {
if ($e->getDetailedError())
} catch (\Exception $e) {
return collect([
'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s (%s)',
@ -66,14 +66,6 @@ abstract class Import
$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->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
@ -59,6 +59,8 @@ class LDIF extends Import
$base64encoded = FALSE;
$attribute = NULL;
$value = '';
// Else its a blank line
}
continue;
@ -67,7 +69,7 @@ class LDIF extends Import
$m = [];
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
switch (Arr::get($m,1)) {
switch ($x=Arr::get($m,1)) {
case 'changetype':
if ($m[2] !== ':')
throw new GeneralException(sprintf('ChangeType cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
@ -123,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->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
else
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
}
@ -131,6 +133,7 @@ class LDIF extends Import
// Start of a new attribute
$base64encoded = ($m[2] === '::');
// @todo Need to parse attributes with ';' options
$attribute = $m[1];
$value = $m[3];
@ -144,7 +147,7 @@ class LDIF extends Import
// We may still have a pending action
if ($action) {
// Add the last attribute;
$o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
@ -156,8 +159,8 @@ class LDIF extends Import
return $result;
}
public function xreadEntry() {
static $haveVersion = FALSE;
public function readEntry() {
static $haveVersion = false;
if ($lines = $this->nextLines()) {
@ -176,7 +179,7 @@ class LDIF extends Import
} else
$changetype = 'add';
$this->template = new Template($this->server_id,NULL,NULL,$changetype);
$this->template = new Template($this->server_id,null,null,$changetype);
switch ($changetype) {
case 'add':
@ -198,7 +201,7 @@ class LDIF extends Import
return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
$this->template->setDN($dn);
$this->template->accept(FALSE,TRUE);
$this->template->accept(false,true);
return $this->getModifyDetails($lines);
@ -218,13 +221,13 @@ class LDIF extends Import
default:
if (! $server->dnExists($dn))
return $this->error(_('Unknown change type'),$lines);
return $this->error(_('Unkown change type'),$lines);
}
} else
return $this->error(_('A valid dn line is required'),$lines);
} else
return FALSE;
return false;
}
}

View File

@ -6,9 +6,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents an LDAP AttributeType
*
@ -323,12 +320,11 @@ 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,bool $structural): void
public function addRequiredByObjectClass(string $name): void
{
if (! $this->required_by_object_classes->has($name))
$this->required_by_object_classes->put($name,$structural);
if (! $this->required_by_object_classes->contains($name))
$this->required_by_object_classes->push($name);
}
/**
@ -336,7 +332,6 @@ 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
{
@ -344,11 +339,6 @@ final class AttributeType extends Base {
$this->used_in_object_classes->put($name,$structural);
}
private function factory(): Attribute
{
return Attribute\Factory::create(dn:'',attribute:$this->name,values:[]);
}
/**
* Gets the names of attributes that are an alias for this attribute (if any).
*
@ -486,28 +476,6 @@ final class AttributeType extends Base {
return $this->used_in_object_classes;
}
/**
* For a list of objectclasses return all parent objectclasses as well
*
* @param Collection $ocs
* @return Collection
*/
public function heirachy(Collection $ocs): Collection
{
$result = collect();
foreach ($ocs as $oc) {
$schema = config('server')
->schema('objectclasses',$oc)
->getParents(TRUE)
->pluck('name');
$result = $result->merge($schema)->push($oc);
}
return $result;
}
/**
* @return bool
* @deprecated use $this->forced_as_may
@ -576,22 +544,21 @@ final class AttributeType extends Base {
*/
public function validation(array $array): ?array
{
// For each item in array, we need to get the OC hierarchy
$heirachy = $this->heirachy(collect($array)
// For each item in array, we need to get the OC heirachy
$heirachy = collect($array)
->filter()
->map(fn($item)=>config('server')
->schema('objectclasses',$item)
->getSupClasses()
->push($item))
->flatten()
->filter());
->unique();
// Get any config validation
$validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[]));
$nolangtag = sprintf('%s.%s.0',$this->name_lc,Entry::TAG_NOTAG);
// Add in schema required by conditions
if (($heirachy->intersect($this->required_by_object_classes->keys())->count() > 0)
if (($heirachy->intersect($this->required_by_object_classes)->count() > 0)
&& (! collect($validation->get($this->name_lc))->contains('required'))) {
$validation
->prepend(array_merge(['required','min:1'],$validation->get($nolangtag,[])),$nolangtag)
->prepend(array_merge(['required','array','min:1',($this->factory()->no_attr_tags ? 'max:1' : NULL)],$validation->get($this->name_lc,[])),$this->name_lc);
$validation->put($this->name_lc,array_merge(['required','min:1'],$validation->get($this->name_lc,[])))
->put($this->name_lc.'.*',array_merge(['required','min:1'],$validation->get($this->name_lc.'.*',[])));
}
return $validation->toArray();

View File

@ -209,8 +209,7 @@ final class Server
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param string|null $connection
* @param Carbon|null $cachetime
* @param null $connection
* @return Entry|null
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
@ -231,7 +230,7 @@ final class Server
/**
* Get the Schema DN
*
* @param string|null $connection
* @param $connection
* @return string
* @throws ObjectNotFoundException
*/
@ -246,21 +245,16 @@ final class Server
* Query the server for a DN and return its children and if those children have children.
*
* @param string $dn
* @param array $attrs
* @return LDAPCollection|NULL
*/
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
public function children(string $dn): ?LDAPCollection
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(array_merge($attrs,[
'hassubordinates', // Needed for the tree to know if an entry has children
'c' // Needed for the tree to show icons for countries
]))
->select(['*','hassubordinates'])
->setDn($dn)
->list()
->orderBy('dn')
->get()) ? $x : NULL;
}
@ -434,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,$object_class->isStructural());
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name);
// Force May
foreach ($object_class->getForceMayAttrs() as $attr_name)
@ -534,6 +528,7 @@ final class Server
*
* @param string $oid
* @return LDAPSyntax|null
* @throws InvalidUsage
*/
public function schemaSyntaxName(string $oid): ?LDAPSyntax
{

View File

@ -39,13 +39,14 @@ class APIController extends Controller
*/
public function children(Request $request): Collection
{
$levels = $request->query('depth',1);
$dn = Crypt::decryptString($request->query('key'));
// Sometimes our key has a command, so we'll ignore it
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
$dn = substr($dn,$x+1);
Log::debug(sprintf('%s: Query [%s]',__METHOD__,$dn));
Log::debug(sprintf('%s: Query [%s] - Levels [%d]',__METHOD__,$dn,$levels));
return (config('server'))
->children($dn)
@ -97,10 +98,11 @@ class APIController extends Controller
/**
* Return the required and additional attributes for an object class
*
* @param Request $request
* @param string $objectclass
* @return array
*/
public function schema_objectclass_attrs(string $objectclass): array
public function schema_objectclass_attrs(Request $request,string $objectclass): array
{
$oc = config('server')->schema('objectclasses',$objectclass);

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@ -41,14 +42,24 @@ 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 View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
public function entry_add(EntryAddRequest $request)
{
if (! old('step',$request->validated('step')))
abort(404);
@ -57,12 +68,11 @@ class HomeController extends Controller
$o = new Entry;
if (count($x=array_filter(old('objectclass',$request->objectclass)))) {
if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
$o->objectclass = $x;
// Also add in our required attributes
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$o->addAttribute($ao,'');
$o->setRDNBase($key['dn']);
}
@ -82,26 +92,24 @@ class HomeController extends Controller
*
* @param Request $request
* @param string $id
* @return \Illuminate\View\View
* @return \Closure|\Illuminate\Contracts\View\View|string
*/
public function entry_attr_add(Request $request,string $id): \Illuminate\View\View
public function entry_attr_add(Request $request,string $id): string
{
$xx = new \stdClass;
$xx->index = 0;
$dn = $request->dn ? Crypt::decrypt($request->dn) : '';
return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create(dn: $dn,attribute: $id,values: [],oc: $request->objectclasses))
$x = $request->noheader
? (string)view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($id,[]))
->with('value',$request->value)
->with('langtag',Entry::TAG_NOTAG)
->with('loop',$xx)
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),new: TRUE,edit: TRUE)
->render();
: (new AttributeType(Factory::create($id,[]),TRUE,collect($request->oc ?: [])))->render();
return $x;
}
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
public function entry_create(EntryAddRequest $request)
{
$key = $this->request_key($request,collect(old()));
@ -131,21 +139,24 @@ class HomeController extends Controller
// @todo when we create an entry, and it already exists, enable a redirect to it
} catch (LdapRecordException $e) {
return Redirect::back()
$request->flash();
switch ($x=$e->getDetailedError()->getErrorCode()) {
case 8:
return Redirect::to('/')
->withInput()
->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
default:
abort(599,$e->getDetailedError()->getErrorMessage());
}
}
return Redirect::to('/')
->withFragment($o->getDNSecure());
}
public function entry_delete(Request $request): \Illuminate\Http\RedirectResponse
public function entry_delete(Request $request)
{
$dn = Crypt::decryptString($request->dn);
@ -185,12 +196,14 @@ class HomeController extends Controller
->with('success',[sprintf('%s: %s',__('Deleted'),$dn)]);
}
public function entry_export(Request $request,string $id): \Illuminate\View\View
public function entry_export(Request $request,string $id)
{
$dn = Crypt::decryptString($id);
$result = (new Entry)
->query()
//->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
//->select(['*'])
->setDn($dn)
->recursive()
->get();
@ -202,13 +215,12 @@ class HomeController extends Controller
/**
* Render an available list of objectclasses for an Entry
*
* @param Request $request
* @return Collection
* @param string $id
* @return mixed
*/
public function entry_objectclass_add(Request $request): Collection
public function entry_objectclass_add(Request $request)
{
$dn = $request->key ? Crypt::decryptString($request->dn) : '';
$oc = Factory::create($dn,'objectclass',$request->oc);
$oc = Factory::create('objectclass',$request->oc);
$ocs = $oc
->structural
@ -230,7 +242,7 @@ class HomeController extends Controller
]);
}
public function entry_password_check(Request $request): Collection
public function entry_password_check(Request $request)
{
$dn = Crypt::decryptString($request->dn);
$o = config('server')->fetch($dn);
@ -238,7 +250,7 @@ class HomeController extends Controller
$password = $o->getObject('userpassword');
$result = collect();
foreach ($password->values->dot() as $key => $value) {
foreach ($password as $key => $value) {
$hash = $password->hash($value);
$compare = Arr::get($request->password,$key);
//Log::debug(sprintf('comparing [%s] with [%s] type [%s]',$value,$compare,$hash::id()),['object'=>$hash]);
@ -253,10 +265,10 @@ class HomeController extends Controller
* Show a confirmation to update a DN
*
* @param EntryRequest $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Http\RedirectResponse
* @throws ObjectNotFoundException
*/
public function entry_pending_update(EntryRequest $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
public function entry_pending_update(EntryRequest $request)
{
$dn = Crypt::decryptString($request->dn);
@ -265,27 +277,22 @@ 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 = [];
$po = $o->getObject('userpassword');
foreach (Arr::dot($request->userpassword) as $dotkey => $value) {
foreach ($request->userpassword as $key => $value) {
// If the password is still the MD5 of the old password, then it hasnt changed
if (($old=Arr::get($po,$dotkey)) && ($value === md5($old))) {
$passwords[$dotkey] = $value;
if (($old=Arr::get($o->userpassword,$key)) && ($value === md5($old))) {
array_push($passwords,$old);
continue;
}
if ($value) {
$type = Arr::get($request->userpassword_hash,$dotkey);
$passwords[$dotkey] = Password::hash_id($type)
->encode($value);
$type = Arr::get($request->userpassword_hash,$key);
array_push($passwords,Password::hash_id($type)->encode($value));
}
}
$o->userpassword = Arr::undot($passwords);
$o->userpassword = $passwords;
}
if (! $o->getDirty())
@ -305,10 +312,8 @@ 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): \Illuminate\Http\RedirectResponse
public function entry_update(EntryRequest $request)
{
$dn = Crypt::decryptString($request->dn);
@ -339,19 +344,22 @@ 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: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
default:
abort(599,$e->getDetailedError()->getErrorMessage());
}
}
return Redirect::to('/')
->withInput()
->with('updated',collect($dirty)->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
->with('updated',collect($dirty)->map(fn($key,$item)=>$o->getObject($item)));
}
/**
@ -360,9 +368,9 @@ class HomeController extends Controller
*
* @param Request $request
* @param Collection|null $old
* @return \Illuminate\View\View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|View
*/
public function frame(Request $request,?Collection $old=NULL): \Illuminate\View\View
public function frame(Request $request,?Collection $old=NULL): View
{
// If our index was not render from a root url, then redirect to it
if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST')
@ -375,14 +383,6 @@ class HomeController extends Controller
: view('frames.'.$key['cmd']))
->with('bases',$this->bases());
// If we are rendering a DN, rebuild our object
if ($key['dn']) {
$o = config('server')->fetch($key['dn']);
foreach (collect(old())->except(['key','dn','step','_token','userpassword_hash','rdn','rdn_value']) as $attr => $value)
$o->{$attr} = $value;
}
return match ($key['cmd']) {
'create' => $view
->with('container',old('container',$key['dn']))
@ -390,14 +390,7 @@ class HomeController extends Controller
'dn' => $view
->with('dn',$key['dn'])
->with('o',$o)
->with('page_actions',collect([
'copy'=>FALSE,
'create'=>TRUE,
'delete'=>TRUE,
'edit'=>TRUE,
'export'=>TRUE,
])),
->with('page_actions',collect(['edit'=>TRUE,'copy'=>TRUE])),
'import' => $view,
@ -408,7 +401,7 @@ class HomeController extends Controller
/**
* This is the main page render function
*/
public function home(Request $request): \Illuminate\View\View
public function home(Request $request)
{
// Did we come here as a result of a redirect
return count(old())
@ -422,11 +415,11 @@ class HomeController extends Controller
*
* @param ImportRequest $request
* @param string $type
* @return \Illuminate\View\View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application
* @throws GeneralException
* @throws VersionException
*/
public function import(ImportRequest $request,string $type): \Illuminate\View\View
public function import(ImportRequest $request,string $type)
{
switch ($type) {
case 'ldif':
@ -454,6 +447,22 @@ 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
*
@ -492,10 +501,10 @@ class HomeController extends Controller
*
* @note Our route will validate that types are valid.
* @param Request $request
* @return \Illuminate\View\View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws InvalidUsage
*/
public function schema_frame(Request $request): \Illuminate\View\View
public function schema_frame(Request $request)
{
// If an invalid key, we'll 404
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
@ -521,9 +530,9 @@ class HomeController extends Controller
* Return the image for the logged in user or anonymous
*
* @param Request $request
* @return \Illuminate\Http\Response
* @return mixed
*/
public function user_image(Request $request): \Illuminate\Http\Response
public function user_image(Request $request)
{
$image = NULL;
$content = NULL;

View File

@ -25,7 +25,6 @@ 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

@ -34,11 +34,10 @@ class EntryAddRequest extends FormRequest
if (request()->method() === 'GET')
return [];
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->intersectByKeys($this->request)
->map(fn($item)=>$item->validation(request()->get('objectclass')))
->filter()
->flatMap(fn($item)=>$item)
->merge([
@ -61,12 +60,6 @@ class EntryAddRequest extends FormRequest
'rdn_value' => 'required_if:step,2|string|min:1',
'step' => 'int|min:1|max:2',
'objectclass'=>[
'required',
'array',
'min:1',
'max:1',
],
'objectclass._null_'=>[
'required',
'array',
'min:1',

View File

@ -13,12 +13,10 @@ class EntryRequest extends FormRequest
*/
public function rules(): array
{
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->intersectByKeys($this->request)
->map(fn($item)=>$item->validation(request()?->get('objectclass') ?: []))
->filter()
->flatMap(fn($item)=>$item)
->toArray();

View File

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

View File

@ -14,20 +14,9 @@ use App\Classes\LDAP\Export\LDIF;
use App\Exceptions\Import\AttributeException;
use App\Exceptions\InvalidUsage;
/**
* An Entry in an LDAP server
*
* @notes https://ldap.com/ldap-dns-and-rdns
*/
class Entry extends Model
{
private const TAG_CHARS = 'a-zA-Z0-9-';
private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']';
public const TAG_NOTAG = '_null_';
// 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;
@ -54,18 +43,13 @@ class Entry extends Model
/**
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes.
*
* This returns an array that should be consistent with $this->attributes
*
* @return array
* @note $this->attributes may not be updated with changes
*/
public function getAttributes(): array
{
return $this->objects
->flatMap(fn($item)=>
($item->no_attr_tags)
? [strtolower($item->name)=>$item->values]
: $item->values
->flatMap(fn($v,$k)=>[strtolower($item->name.($k !== self::TAG_NOTAG ? ';'.$k : ''))=>$v]))
->map(fn($item)=>$item->values)
->toArray();
}
@ -78,10 +62,12 @@ class Entry extends Model
{
$key = $this->normalizeAttributeKey($key);
list($attribute,$tag) = $this->keytag($key);
// @todo Silently ignore keys of language tags - we should work with them
if (str_contains($key,';'))
return TRUE;
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute)))
|| (! $this->getObject($attribute)->isDirty());
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key)))
|| (! $this->getObject($key)->isDirty());
}
public static function query(bool $noattrs=false): Builder
@ -96,9 +82,7 @@ class Entry extends Model
/**
* As attribute values are updated, or new ones created, we need to mirror that
* into our $objects. This is called when we $o->key = $value
*
* This function should update $this->attributes and correctly reflect changes in $this->objects
* into our $objects
*
* @param string $key
* @param mixed $value
@ -106,16 +90,16 @@ class Entry extends Model
*/
public function setAttribute(string $key, mixed $value): static
{
foreach ($value as $k => $v)
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
parent::setAttribute($key,$value);
$key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($value);
if ((! $this->objects->get($key)) && $value) {
$this->objects->put($key,Factory::create($key,$value));
$this->objects->put($key,$o);
} elseif ($this->objects->get($key)) {
$this->objects->get($key)->value = $this->attributes[$key];
}
return $this;
}
@ -149,106 +133,69 @@ 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 collect(explode(',',$this->getDn()))->reverse()->join(',');
return $this->getDn();
}
/* METHODS */
/**
* 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
public function addAttribute(string $key,mixed $value): void
{
// While $value is mixed, it can only be a string
if (! is_string($value))
throw new \Exception('value should be a string');
$key = $this->normalizeAttributeKey(strtolower($key));
$key = $this->normalizeAttributeKey($key);
// If the attribute name has tags
list($attribute,$tag) = $this->keytag($key);
if (! config('server')->schema('attributetypes')->has($key))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
if (! config('server')->schema('attributetypes')->has($attribute))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
if ($x=$this->objects->get($key)) {
$x->addValue($value);
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]);
$o->addValue($tag,[$value]);
$this->objects->put($attribute,$o);
}
/**
* Export this record
*
* @param string $method
* @param string $scope
* @return string
* @throws \Exception
*/
public function export(string $method,string $scope): string
{
// @todo To implement
switch ($scope) {
case 'base':
case 'one':
case 'sub':
break;
default:
throw new \Exception('Export scope unknown:'.$scope);
}
switch ($method) {
case 'ldif':
return new LDIF(collect($this));
default:
throw new \Exception('Export method not implemented:'.$method);
} else {
$this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($value)));
}
}
/**
* Convert all our attribute values into an array of Objects
*
* @param array $attributes
* @return Collection
*/
private function getAttributesAsObjects(): Collection
public function getAttributesAsObjects(): Collection
{
$result = collect();
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attrtag => $values) {
list($attribute,$tags) = $this->keytag($attrtag);
$orig = Arr::get($this->original,$attrtag,[]);
foreach ($this->attributes as $attribute => $value) {
// If the attribute name has language 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(
$this->dn,
$attribute,
[$tags=>$orig],
$entry_oc,
));
$o = Arr::get($result,$attribute,Factory::create($attribute,[]));
$o->setLangTag($matches[3],$value);
$o->addValue($tags,$values);
$o->addValueOld($tags,Arr::get($this->original,$attrtag));
} else {
$o = Factory::create($attribute,$value);
}
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,[]));
$result->put($attribute,$o);
}
}
$sort = collect(config('pla.attr_display_order',[]))->map(fn($item)=>strtolower($item));
@ -288,7 +235,7 @@ class Entry extends Model
{
$result = collect();
foreach ($this->getObject('objectclass')->values as $oc)
foreach ($this->objectclass as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result;
@ -315,38 +262,6 @@ class Entry extends Model
->filter(fn($item)=>$item->is_internal);
}
/**
* Identify the language tags (RFC 3866) used by this entry
*
* @return Collection
*/
public function getLangTags(): Collection
{
return $this->getObjects()
->filter(fn($item)=>! $item->no_attr_tags)
->map(fn($item)=>$item
->values
->keys()
->filter(fn($item)=>preg_match(sprintf('/%s+;?/',self::TAG_CHARS_LANG),$item))
->map(fn($item)=>preg_replace('/lang-/','',$item))
)
->filter(fn($item)=>$item->count());
}
/**
* Of all the items with lang tags, which ones have more than 1 lang tag
*
* @return Collection
*/
public function getLangMultiTags(): Collection
{
return $this->getLangTags()
->map(fn($item)=>$item->values()
->map(fn($item)=>explode(';',$item))
->filter(fn($item)=>count($item) > 1))
->filter(fn($item)=>$item->count());
}
/**
* Get an attribute as an object
*
@ -372,36 +287,10 @@ class Entry extends Model
return $this->objects;
}
/**
* Find other attribute tags used by this entry
*
* @return Collection
*/
public function getOtherTags(): Collection
{
return $this->getObjects()
->filter(fn($item)=>! $item->no_attr_tags)
->map(fn($item)=>$item
->values
->keys()
->filter(fn($item)=>
$item && collect(explode(';',$item))->filter(
fn($item)=>
(! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item))
&& (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
)
->count())
)
->filter(fn($item)=>$item->count());
}
/**
* Return a list of attributes without any values
*
* @return Collection
* @todo Dont show attributes that are not provided by an objectclass, make a new function to show those
* This is for dynamic list items eg: labeledURI, which are not editable.
* We can highlight those values that are as a result of a dynamic module
*/
public function getMissingAttributes(): Collection
{
@ -411,8 +300,8 @@ class Entry extends Model
private function getRDNObject(): Attribute\RDN
{
$o = new Attribute\RDN('','dn',['']);
// @todo for an existing object, rdnbase would be null, so dynamically get it from the DN.
$o = new Attribute\RDN('dn',['']);
// @todo for an existing object, return the base.
$o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
@ -422,22 +311,12 @@ class Entry extends Model
/**
* Return this list of user attributes
*
* @param string $tag If null return all tags
* @return Collection
*/
public function getVisibleAttributes(string $tag=''): Collection
public function getVisibleAttributes(): Collection
{
static $cache = [];
if (! Arr::get($cache,$tag ?: '_all_')) {
$ot = $this->getOtherTags();
$cache[$tag ?: '_all_'] = $this->objects
->filter(fn($item)=>(! $item->is_internal) && ((! $item->no_attr_tags) || (! $tag) || ($tag === Entry::TAG_NOTAG)))
->filter(fn($item)=>(! $tag) || $ot->has($item->name_lc) || count($item->tagValues($tag)) > 0);
}
return $cache[$tag ?: '_all_'];
return $this->objects
->filter(fn($item)=>! $item->is_internal);
}
public function hasAttribute(int|string $key): bool
@ -446,6 +325,36 @@ class Entry extends Model
->has($key);
}
/**
* Export this record
*
* @param string $method
* @param string $scope
* @return string
* @throws \Exception
*/
public function export(string $method,string $scope): string
{
// @todo To implement
switch ($scope) {
case 'base':
case 'one':
case 'sub':
break;
default:
throw new \Exception('Export scope unknown:'.$scope);
}
switch ($method) {
case 'ldif':
return new LDIF(collect($this));
default:
throw new \Exception('Export method not implemented:'.$method);
}
}
/**
* Return an icon for a DN based on objectClass
*
@ -453,97 +362,69 @@ class Entry extends Model
*/
public function icon(): string
{
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
$objectclasses = array_map('strtolower',$this->objectclass);
// Return icon based upon objectClass value
if ($objectclasses->intersect([
'account',
'inetorgperson',
'organizationalperson',
'person',
'posixaccount',
])->count())
if (in_array('person',$objectclasses) ||
in_array('organizationalperson',$objectclasses) ||
in_array('inetorgperson',$objectclasses) ||
in_array('account',$objectclasses) ||
in_array('posixaccount',$objectclasses))
return 'fas fa-user';
elseif ($objectclasses->contains('organization'))
elseif (in_array('organization',$objectclasses))
return 'fas fa-university';
elseif ($objectclasses->contains('organizationalunit'))
elseif (in_array('organizationalunit',$objectclasses))
return 'fas fa-object-group';
elseif ($objectclasses->intersect([
'posixgroup',
'groupofnames',
'groupofuniquenames',
'group',
])->count())
elseif (in_array('posixgroup',$objectclasses) ||
in_array('groupofnames',$objectclasses) ||
in_array('groupofuniquenames',$objectclasses) ||
in_array('group',$objectclasses))
return 'fas fa-users';
elseif ($objectclasses->intersect([
'dcobject',
'domainrelatedobject',
'domain',
'builtindomain',
])->count())
elseif (in_array('dcobject',$objectclasses) ||
in_array('domainrelatedobject',$objectclasses) ||
in_array('domain',$objectclasses) ||
in_array('builtindomain',$objectclasses))
return 'fas fa-network-wired';
elseif ($objectclasses->contains('alias'))
elseif (in_array('alias',$objectclasses))
return 'fas fa-theater-masks';
elseif ($objectclasses->contains('country'))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0)));
elseif (in_array('country',$objectclasses))
return sprintf('flag %s',strtolower(Arr::get($this->c,0)));
elseif ($objectclasses->contains('device'))
elseif (in_array('device',$objectclasses))
return 'fas fa-mobile-alt';
elseif ($objectclasses->contains('document'))
elseif (in_array('document',$objectclasses))
return 'fas fa-file-alt';
elseif ($objectclasses->contains('iphost'))
elseif (in_array('iphost',$objectclasses))
return 'fas fa-wifi';
elseif ($objectclasses->contains('room'))
elseif (in_array('room',$objectclasses))
return 'fas fa-door-open';
elseif ($objectclasses->contains('server'))
elseif (in_array('server',$objectclasses))
return 'fas fa-server';
elseif ($objectclasses->contains('openldaprootdse'))
elseif (in_array('openldaprootdse',$objectclasses))
return 'fas fa-info';
// Default
return 'fa-fw fas fa-cog';
}
/**
* Given an LDAP attribute, this will return the attribute name and the tag
* eg: description;lang-cn will return [description,lang-cn]
*
* @param string $key
* @return array
*/
private function keytag(string $key): array
{
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = self::TAG_NOTAG;
}
return [$attribute,$tags];
}
/**
* 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

@ -20,7 +20,7 @@ class HasStructuralObjectClass implements ValidationRule
*/
public function validate(string $attribute,mixed $value,Closure $fail): void
{
foreach (collect($value)->dot() as $item)
foreach ($value as $item)
if ($item && config('server')->schema('objectclasses',$item)->isStructural())
return;

View File

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

View File

@ -2,12 +2,9 @@
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class Attribute extends Component
{
@ -15,32 +12,30 @@ class Attribute extends Component
public bool $edit;
public bool $new;
public bool $old;
public string $langtag;
public ?string $na; // Text to render if the LDAPAttribute is null
public ?string $na;
/**
* Create a new component instance.
*/
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,?string $na=NULL)
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,?string $na=NULL)
{
$this->o = $o;
$this->edit = $edit;
$this->old = $old;
$this->new = $new;
$this->langtag = $langtag;
$this->na = $na;
}
/**
* Get the view / contents that represent the component.
*
* @return View|string
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render(): View|string
public function render()
{
return $this->o
? $this->o
->render(edit: $this->edit,old: $this->old,new: $this->new)
->render($this->edit,$this->old,$this->new)
: $this->na;
}
}

View File

@ -2,39 +2,37 @@
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class AttributeType extends Component
{
private LDAPAttribute $o;
private bool $new;
private bool $edit;
private string $langtag;
public Collection $oc;
public LDAPAttribute $o;
public bool $new;
/**
* Create a new component instance.
*/
public function __construct(LDAPAttribute $o,bool $new=FALSE,bool $edit=FALSE,string $langtag=Entry::TAG_NOTAG)
public function __construct(LDAPAttribute $o,bool $new=FALSE,?Collection $oc=NULL)
{
$this->o = $o;
$this->oc = $oc;
$this->new = $new;
$this->edit = $edit;
$this->langtag = $langtag;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View
public function render(): View|Closure|string
{
return view('components.attribute-type')
->with('o',$this->o)
->with('new',$this->new)
->with('edit',$this->edit)
->with('langtag',$this->langtag);
->with('oc',$this->oc)
->with('new',$this->new);
}
}

View File

@ -30,7 +30,6 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->trustProxies(at: [
'10.0.0.0/8',
'127.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/12',
]);

View File

@ -7,7 +7,6 @@
"require": {
"ext-fileinfo": "*",
"ext-ldap": "*",
"ext-openssl": "*",
"php": "^8.4",
"directorytree/ldaprecord-laravel": "^3.0",
"laravel/framework": "^11.9",

309
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ddfbe582d0c27ef08ff1410102ccda28",
"content-hash": "2f0a146742112814f55f6a4e5bd12da3",
"packages": [
{
"name": "brick/math",
@ -288,16 +288,16 @@
},
{
"name": "directorytree/ldaprecord-laravel",
"version": "v3.4.1",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/DirectoryTree/LdapRecord-Laravel.git",
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e"
"reference": "bb0aa206723ed07e2b42eadd7311d5949cc770dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/15f56e01319852d41023633d3688ac4aa139aa6e",
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/bb0aa206723ed07e2b42eadd7311d5949cc770dd",
"reference": "bb0aa206723ed07e2b42eadd7311d5949cc770dd",
"shasum": ""
},
"require": {
@ -343,7 +343,7 @@
],
"support": {
"issues": "https://github.com/DirectoryTree/LdapRecord-Laravel/issues",
"source": "https://github.com/DirectoryTree/LdapRecord-Laravel/tree/v3.4.1"
"source": "https://github.com/DirectoryTree/LdapRecord-Laravel/tree/v3.4.0"
},
"funding": [
{
@ -351,7 +351,7 @@
"type": "github"
}
],
"time": "2025-03-21T19:16:44+00:00"
"time": "2025-02-26T01:41:53+00:00"
},
{
"name": "doctrine/inflector",
@ -588,16 +588,16 @@
},
{
"name": "egulias/email-validator",
"version": "4.0.4",
"version": "4.0.3",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
"reference": "b115554301161fa21467629f1e1391c1936de517"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517",
"reference": "b115554301161fa21467629f1e1391c1936de517",
"shasum": ""
},
"require": {
@ -643,7 +643,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
"source": "https://github.com/egulias/EmailValidator/tree/4.0.3"
},
"funding": [
{
@ -651,7 +651,7 @@
"type": "github"
}
],
"time": "2025-03-06T22:45:56+00:00"
"time": "2024-12-27T00:36:43+00:00"
},
{
"name": "fruitcake/php-cors",
@ -788,16 +788,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.3",
"version": "7.9.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
"reference": "d281ed313b989f213357e3be1a179f02196ac99b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": ""
},
"require": {
@ -894,7 +894,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.3"
"source": "https://github.com/guzzle/guzzle/tree/7.9.2"
},
"funding": [
{
@ -910,20 +910,20 @@
"type": "tidelift"
}
],
"time": "2025-03-27T13:37:11+00:00"
"time": "2024-07-24T11:22:20+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.2.0",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"shasum": ""
},
"require": {
@ -977,7 +977,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.2.0"
"source": "https://github.com/guzzle/promises/tree/2.0.4"
},
"funding": [
{
@ -993,20 +993,20 @@
"type": "tidelift"
}
],
"time": "2025-03-27T13:27:01+00:00"
"time": "2024-10-17T10:06:22+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.1",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
"reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"shasum": ""
},
"require": {
@ -1093,7 +1093,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
"source": "https://github.com/guzzle/psr7/tree/2.7.0"
},
"funding": [
{
@ -1109,7 +1109,7 @@
"type": "tidelift"
}
],
"time": "2025-03-27T12:30:47+00:00"
"time": "2024-07-18T11:15:46+00:00"
},
{
"name": "guzzlehttp/uri-template",
@ -1199,16 +1199,16 @@
},
{
"name": "laravel/framework",
"version": "v11.44.2",
"version": "v11.44.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"shasum": ""
},
"require": {
@ -1410,7 +1410,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-03-12T14:34:30+00:00"
"time": "2025-03-05T15:34:10+00:00"
},
{
"name": "laravel/prompts",
@ -1537,16 +1537,16 @@
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.4",
"version": "v2.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841"
"reference": "f379c13663245f7aa4512a7869f62eb14095f23f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f379c13663245f7aa4512a7869f62eb14095f23f",
"reference": "f379c13663245f7aa4512a7869f62eb14095f23f",
"shasum": ""
},
"require": {
@ -1594,7 +1594,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2025-03-19T13:51:03+00:00"
"time": "2025-02-11T15:03:05+00:00"
},
{
"name": "laravel/ui",
@ -2212,16 +2212,16 @@
},
{
"name": "monolog/monolog",
"version": "3.9.0",
"version": "3.8.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"shasum": ""
},
"require": {
@ -2299,7 +2299,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
"source": "https://github.com/Seldaek/monolog/tree/3.8.1"
},
"funding": [
{
@ -2311,20 +2311,20 @@
"type": "tidelift"
}
],
"time": "2025-03-24T10:02:05+00:00"
"time": "2024-12-05T17:15:07+00:00"
},
{
"name": "nesbot/carbon",
"version": "3.9.0",
"version": "3.8.6",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d"
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6d16a8a015166fe54e22c042e0805c5363aef50d",
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"shasum": ""
},
"require": {
@ -2417,7 +2417,7 @@
"type": "tidelift"
}
],
"time": "2025-03-27T12:57:33+00:00"
"time": "2025-02-20T17:33:38+00:00"
},
{
"name": "nette/schema",
@ -2483,16 +2483,16 @@
},
{
"name": "nette/utils",
"version": "v4.0.6",
"version": "v4.0.5",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "ce708655043c7050eb050df361c5e313cf708309"
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
"reference": "ce708655043c7050eb050df361c5e313cf708309",
"url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"shasum": ""
},
"require": {
@ -2563,9 +2563,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.6"
"source": "https://github.com/nette/utils/tree/v4.0.5"
},
"time": "2025-03-30T21:06:30+00:00"
"time": "2024-08-07T15:39:19+00:00"
},
{
"name": "nunomaduro/termwind",
@ -3187,16 +3187,16 @@
},
{
"name": "ramsey/collection",
"version": "2.1.1",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
"url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"shasum": ""
},
"require": {
@ -3257,9 +3257,9 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.1.1"
"source": "https://github.com/ramsey/collection/tree/2.1.0"
},
"time": "2025-03-22T05:38:12+00:00"
"time": "2025-03-02T04:48:29+00:00"
},
{
"name": "ramsey/uuid",
@ -3429,16 +3429,16 @@
},
{
"name": "symfony/console",
"version": "v7.2.5",
"version": "v7.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"shasum": ""
},
"require": {
@ -3502,7 +3502,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.2.5"
"source": "https://github.com/symfony/console/tree/v7.2.1"
},
"funding": [
{
@ -3518,7 +3518,7 @@
"type": "tidelift"
}
],
"time": "2025-03-12T08:11:12+00:00"
"time": "2024-12-11T03:49:26+00:00"
},
{
"name": "symfony/css-selector",
@ -3654,16 +3654,16 @@
},
{
"name": "symfony/error-handler",
"version": "v7.2.5",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
"reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b"
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b",
"reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/aabf79938aa795350c07ce6464dd1985607d95d5",
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5",
"shasum": ""
},
"require": {
@ -3709,7 +3709,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/error-handler/tree/v7.2.5"
"source": "https://github.com/symfony/error-handler/tree/v7.2.4"
},
"funding": [
{
@ -3725,7 +3725,7 @@
"type": "tidelift"
}
],
"time": "2025-03-03T07:12:39+00:00"
"time": "2025-02-02T20:27:07+00:00"
},
{
"name": "symfony/event-dispatcher",
@ -3949,16 +3949,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.2.5",
"version": "v7.2.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126"
"reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126",
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0",
"reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0",
"shasum": ""
},
"require": {
@ -4007,7 +4007,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.2.5"
"source": "https://github.com/symfony/http-foundation/tree/v7.2.3"
},
"funding": [
{
@ -4023,20 +4023,20 @@
"type": "tidelift"
}
],
"time": "2025-03-25T15:54:33+00:00"
"time": "2025-01-17T10:56:55+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.2.5",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54"
"reference": "9f1103734c5789798fefb90e91de4586039003ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f1103734c5789798fefb90e91de4586039003ed",
"reference": "9f1103734c5789798fefb90e91de4586039003ed",
"shasum": ""
},
"require": {
@ -4121,7 +4121,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.2.5"
"source": "https://github.com/symfony/http-kernel/tree/v7.2.4"
},
"funding": [
{
@ -4137,7 +4137,7 @@
"type": "tidelift"
}
],
"time": "2025-03-28T13:32:50+00:00"
"time": "2025-02-26T11:01:22+00:00"
},
{
"name": "symfony/mailer",
@ -4941,16 +4941,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.5",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"shasum": ""
},
"require": {
@ -4982,7 +4982,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.5"
"source": "https://github.com/symfony/process/tree/v7.2.4"
},
"funding": [
{
@ -4998,7 +4998,7 @@
"type": "tidelift"
}
],
"time": "2025-03-13T12:21:46+00:00"
"time": "2025-02-05T08:33:46+00:00"
},
{
"name": "symfony/routing",
@ -5856,16 +5856,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.15.3",
"version": "v3.15.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037"
"reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/4ccab20844d18c5af08b68d310e7151a791c3037",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/0bc1e1361e7fffc2be156f46ad1fba6927c01729",
"reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729",
"shasum": ""
},
"require": {
@ -5876,6 +5876,9 @@
"php-debugbar/php-debugbar": "~2.1.1",
"symfony/finder": "^6|^7"
},
"conflict": {
"maximebf/debugbar": "*"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench-dusk": "^7|^8|^9|^10",
@ -5925,7 +5928,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.3"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.2"
},
"funding": [
{
@ -5937,7 +5940,7 @@
"type": "github"
}
],
"time": "2025-04-08T15:11:06+00:00"
"time": "2025-02-25T15:25:22+00:00"
},
{
"name": "fakerphp/faker",
@ -6004,16 +6007,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.0",
"version": "2.17.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
"reference": "075bc0c26631110584175de6523ab3f1652eb28e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e",
"reference": "075bc0c26631110584175de6523ab3f1652eb28e",
"shasum": ""
},
"require": {
@ -6063,7 +6066,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.0"
"source": "https://github.com/filp/whoops/tree/2.17.0"
},
"funding": [
{
@ -6071,7 +6074,7 @@
"type": "github"
}
],
"time": "2025-03-15T12:00:00+00:00"
"time": "2025-01-25T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -6327,39 +6330,38 @@
},
{
"name": "nunomaduro/collision",
"version": "v8.8.0",
"version": "v8.6.1",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8"
"reference": "86f003c132143d5a2ab214e19933946409e0cae7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/86f003c132143d5a2ab214e19933946409e0cae7",
"reference": "86f003c132143d5a2ab214e19933946409e0cae7",
"shasum": ""
},
"require": {
"filp/whoops": "^2.18.0",
"filp/whoops": "^2.16.0",
"nunomaduro/termwind": "^2.3.0",
"php": "^8.2.0",
"symfony/console": "^7.2.5"
"symfony/console": "^7.2.1"
},
"conflict": {
"laravel/framework": "<11.44.2 || >=13.0.0",
"phpunit/phpunit": "<11.5.15 || >=13.0.0"
"laravel/framework": "<11.39.1 || >=13.0.0",
"phpunit/phpunit": "<11.5.3 || >=12.0.0"
},
"require-dev": {
"brianium/paratest": "^7.8.3",
"larastan/larastan": "^3.2",
"laravel/framework": "^11.44.2 || ^12.6",
"laravel/pint": "^1.21.2",
"laravel/sail": "^1.41.0",
"laravel/sanctum": "^4.0.8",
"laravel/tinker": "^2.10.1",
"orchestra/testbench-core": "^9.12.0 || ^10.1",
"pestphp/pest": "^3.8.0",
"sebastian/environment": "^7.2.0 || ^8.0"
"larastan/larastan": "^2.9.12",
"laravel/framework": "^11.39.1",
"laravel/pint": "^1.20.0",
"laravel/sail": "^1.40.0",
"laravel/sanctum": "^4.0.7",
"laravel/tinker": "^2.10.0",
"orchestra/testbench-core": "^9.9.2",
"pestphp/pest": "^3.7.3",
"sebastian/environment": "^6.1.0 || ^7.2.0"
},
"type": "library",
"extra": {
@ -6422,7 +6424,7 @@
"type": "patreon"
}
],
"time": "2025-04-03T14:33:09+00:00"
"time": "2025-01-23T13:41:43+00:00"
},
{
"name": "phar-io/manifest",
@ -6937,16 +6939,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.17",
"version": "11.5.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c"
"reference": "3946ac38410be7440186c6e74584f31b15107fc7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3946ac38410be7440186c6e74584f31b15107fc7",
"reference": "3946ac38410be7440186c6e74584f31b15107fc7",
"shasum": ""
},
"require": {
@ -6966,14 +6968,14 @@
"phpunit/php-text-template": "^4.0.1",
"phpunit/php-timer": "^7.0.1",
"sebastian/cli-parser": "^3.0.2",
"sebastian/code-unit": "^3.0.3",
"sebastian/comparator": "^6.3.1",
"sebastian/code-unit": "^3.0.2",
"sebastian/comparator": "^6.3.0",
"sebastian/diff": "^6.0.2",
"sebastian/environment": "^7.2.0",
"sebastian/exporter": "^6.3.0",
"sebastian/global-state": "^7.0.2",
"sebastian/object-enumerator": "^6.0.1",
"sebastian/type": "^5.1.2",
"sebastian/type": "^5.1.0",
"sebastian/version": "^5.0.2",
"staabm/side-effects-detector": "^1.0.5"
},
@ -7018,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.17"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.11"
},
"funding": [
{
@ -7034,7 +7036,7 @@
"type": "tidelift"
}
],
"time": "2025-04-08T07:59:11+00:00"
"time": "2025-03-05T07:36:02+00:00"
},
{
"name": "sebastian/cli-parser",
@ -7095,16 +7097,16 @@
},
{
"name": "sebastian/code-unit",
"version": "3.0.3",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit.git",
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64"
"reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64",
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
"reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
"shasum": ""
},
"require": {
@ -7140,7 +7142,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit/issues",
"security": "https://github.com/sebastianbergmann/code-unit/security/policy",
"source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3"
"source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2"
},
"funding": [
{
@ -7148,7 +7150,7 @@
"type": "github"
}
],
"time": "2025-03-19T07:56:08+00:00"
"time": "2024-12-12T09:59:06+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@ -7208,16 +7210,16 @@
},
{
"name": "sebastian/comparator",
"version": "6.3.1",
"version": "6.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959"
"reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
"reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
"shasum": ""
},
"require": {
@ -7236,7 +7238,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.3-dev"
"dev-main": "6.2-dev"
}
},
"autoload": {
@ -7276,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.1"
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0"
},
"funding": [
{
@ -7284,7 +7286,7 @@
"type": "github"
}
],
"time": "2025-03-07T06:57:01+00:00"
"time": "2025-01-06T10:28:19+00:00"
},
{
"name": "sebastian/complexity",
@ -7853,16 +7855,16 @@
},
{
"name": "sebastian/type",
"version": "5.1.2",
"version": "5.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e"
"reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"shasum": ""
},
"require": {
@ -7898,7 +7900,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy",
"source": "https://github.com/sebastianbergmann/type/tree/5.1.2"
"source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
},
"funding": [
{
@ -7906,7 +7908,7 @@
"type": "github"
}
],
"time": "2025-03-18T13:35:50+00:00"
"time": "2024-09-17T13:12:04+00:00"
},
{
"name": "sebastian/version",
@ -8453,7 +8455,6 @@
"platform": {
"ext-fileinfo": "*",
"ext-ldap": "*",
"ext-openssl": "*",
"php": "^8.4"
},
"platform-dev": {},

View File

@ -122,47 +122,54 @@ return [
*/
'validation' => [
'objectclass' => [
'objectclass.*'=>[
'objectclass'=>[
'required',
'array',
'min:1',
new HasStructuralObjectClass,
]
],
'gidnumber' => [
'gidnumber.*'=> [
'gidnumber'=> [
'sometimes',
'array',
'max:1'
],
'gidnumber.*.*' => [
'gidnumber.*' => [
'nullable',
'integer',
'max:65535'
]
],
'mail' => [
'mail.*'=>[
'mail'=>[
'sometimes',
'array',
'min:1'
],
'mail.*.*' => [
'mail.*' => [
'nullable',
'email'
]
],
'userpassword' => [
'userpassword.*' => [
'userpassword' => [
'sometimes',
'array',
'min:1'
],
'userpassword.*.*' => [
'userpassword.*' => [
'nullable',
'min:8'
]
],
'uidnumber' => [
'uidnumber.*' => [
'uidnumber' => [
'sometimes',
'array',
'max:1'
],
'uidnumber.*.*' => [
'uidnumber.*' => [
'nullable',
'integer',
'max:65535'

View File

@ -54,7 +54,6 @@
1.3.6.1.4.1.42.2.27.8.5.1:passwordPolicyRequest
1.3.6.1.4.1.42.2.27.9.5.2:GetEffectiveRights control::May be used to determine what operations a given user may perform on a specified entry.
1.3.6.1.4.1.1466.101.119.1:Dynamic Directory Services Refresh Request:RFC 2589
1.3.6.1.4.1.1466.115.121.1.25:"guide" syntax-name:RFC 4517
1.3.6.1.4.1.1466.20036:LDAP_NOTICE_OF_DISCONNECTION
1.3.6.1.4.1.1466.20037:Transport Layer Security Extension:RFC 2830:This operation provides for TLS establishment in an LDAP association and is defined in terms of an LDAP extended request.
1.3.6.1.4.1.1466.29539.1:LDAP_CONTROL_ATTR_SIZELIMIT

336
package-lock.json generated
View File

@ -10,7 +10,6 @@
"animate-sass": "^0.8.2",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.11.3",
"jquery": "^3.6.3",
"jquery-ui": "^1.13.2",
"jquery.fancytree": "^2.38.3",
@ -62,21 +61,21 @@
}
},
"node_modules/@babel/core": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.10",
"@babel/generator": "^7.26.9",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-module-transforms": "^7.26.0",
"@babel/helpers": "^7.26.10",
"@babel/parser": "^7.26.10",
"@babel/helpers": "^7.26.9",
"@babel/parser": "^7.26.9",
"@babel/template": "^7.26.9",
"@babel/traverse": "^7.26.10",
"@babel/types": "^7.26.10",
"@babel/traverse": "^7.26.9",
"@babel/types": "^7.26.9",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -101,13 +100,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
"integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0",
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@ -129,12 +128,12 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
"integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
"integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.26.8",
"@babel/compat-data": "^7.26.5",
"@babel/helper-validator-option": "^7.25.9",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
@ -154,9 +153,9 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz",
"integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz",
"integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@ -164,7 +163,7 @@
"@babel/helper-optimise-call-expression": "^7.25.9",
"@babel/helper-replace-supers": "^7.26.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
"@babel/traverse": "^7.27.0",
"@babel/traverse": "^7.26.9",
"semver": "^6.3.1"
},
"engines": {
@ -184,9 +183,9 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz",
"integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==",
"version": "7.26.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
"integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@ -210,9 +209,9 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz",
"integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==",
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
"integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
@ -378,25 +377,25 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.0"
"@babel/types": "^7.26.9"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -651,12 +650,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz",
"integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==",
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz",
"integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.26.5"
"@babel/helper-plugin-utils": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
@ -1188,12 +1187,12 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz",
"integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==",
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz",
"integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.26.5",
"@babel/helper-plugin-utils": "^7.25.9",
"regenerator-transform": "^0.15.2"
},
"engines": {
@ -1235,15 +1234,15 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
"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==",
"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==",
"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.11.0",
"babel-plugin-polyfill-corejs3": "^0.10.6",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"semver": "^6.3.1"
},
@ -1325,9 +1324,9 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz",
"integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==",
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz",
"integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.26.5"
@ -1485,6 +1484,19 @@
"@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",
@ -1509,9 +1521,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -1521,30 +1533,30 @@
}
},
"node_modules/@babel/template": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
"integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.27.0",
"@babel/parser": "^7.27.0",
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0",
"@babel/generator": "^7.26.9",
"@babel/parser": "^7.26.9",
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1553,9 +1565,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@ -2021,9 +2033,9 @@
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
@ -2040,9 +2052,9 @@
}
},
"node_modules/@types/babel__traverse": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.20.7"
@ -2117,9 +2129,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"license": "MIT"
},
"node_modules/@types/express": {
@ -2248,12 +2260,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"version": "22.13.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
"undici-types": "~6.20.0"
}
},
"node_modules/@types/node-forge": {
@ -2335,9 +2347,9 @@
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
@ -2767,9 +2779,9 @@
"license": "MIT"
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"funding": [
{
"type": "opencollective",
@ -2786,11 +2798,11 @@
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001646",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1",
"picocolors": "^1.0.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
@ -2804,9 +2816,9 @@
}
},
"node_modules/axios": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -2834,13 +2846,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz",
"integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==",
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
"integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.6",
"@babel/helper-define-polyfill-provider": "^0.6.4",
"@babel/helper-define-polyfill-provider": "^0.6.3",
"semver": "^6.3.1"
},
"peerDependencies": {
@ -2857,25 +2869,25 @@
}
},
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.3",
"core-js-compat": "^3.40.0"
"@babel/helper-define-polyfill-provider": "^0.6.2",
"core-js-compat": "^3.38.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz",
"integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==",
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
"integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.4"
"@babel/helper-define-polyfill-provider": "^0.6.3"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@ -3023,9 +3035,9 @@
"license": "ISC"
},
"node_modules/bootstrap": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz",
"integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"funding": [
{
"type": "github",
@ -3041,22 +3053,6 @@
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -3315,9 +3311,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001713",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz",
"integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==",
"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==",
"funding": [
{
"type": "opencollective",
@ -4328,9 +4324,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
"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==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -5292,9 +5288,9 @@
}
},
"node_modules/html-entities": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"funding": [
{
"type": "github",
@ -5447,9 +5443,9 @@
}
},
"node_modules/http-parser-js": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz",
"integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==",
"license": "MIT"
},
"node_modules/http-proxy": {
@ -5467,9 +5463,9 @@
}
},
"node_modules/http-proxy-middleware": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz",
"integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.8",
@ -5634,9 +5630,9 @@
}
},
"node_modules/immutable": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
"license": "MIT"
},
"node_modules/import-fresh": {
@ -5923,9 +5919,9 @@
}
},
"node_modules/jquery.fancytree": {
"version": "2.38.5",
"resolved": "https://registry.npmjs.org/jquery.fancytree/-/jquery.fancytree-2.38.5.tgz",
"integrity": "sha512-6ntTplhfYKWz74GLpeeE9B62VqhsF+bd80gLZRDD1gl7Vv9WTqqQrCsrGMMu0PB6JLhNOXhf17xIcYpARG+N3g==",
"version": "2.38.4",
"resolved": "https://registry.npmjs.org/jquery.fancytree/-/jquery.fancytree-2.38.4.tgz",
"integrity": "sha512-f4Fv5jZiZ6pBml/9txcJRAQDZpqQGGoJ8BUbicZKcO4CpgGbqBX9W7eQFwEaKQS0bxdVBLbqWQ9RoUK05ON2kQ==",
"license": "MIT",
"peerDependencies": {
"jquery": ">=1.9"
@ -6098,9 +6094,9 @@
}
},
"node_modules/less": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz",
"integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz",
"integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
@ -6112,7 +6108,7 @@
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
"node": ">=6"
},
"optionalDependencies": {
"errno": "^0.1.1",
@ -6549,9 +6545,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@ -8240,9 +8236,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.86.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz",
"integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
"version": "1.85.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz",
"integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@ -8832,9 +8828,9 @@
}
},
"node_modules/std-env": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz",
"integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==",
"license": "MIT"
},
"node_modules/stream-browserify": {
@ -9031,9 +9027,9 @@
}
},
"node_modules/terser-webpack-plugin": {
"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==",
"version": "5.3.13",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.13.tgz",
"integrity": "sha512-JG3pBixF6kx2o0Yfz2K6pqh72DpwTI08nooHd06tcj5WyIt5SsSiUYqRT+kemrGUNSuSzVhwfZ28aO8gogajNQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
@ -9194,9 +9190,9 @@
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@ -9431,9 +9427,9 @@
}
},
"node_modules/webpack": {
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"version": "5.98.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",

View File

@ -15,7 +15,6 @@
"animate-sass": "^0.8.2",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.11.3",
"jquery": "^3.6.3",
"jquery-ui": "^1.13.2",
"jquery.fancytree": "^2.38.3",

View File

@ -1 +1 @@
v2.1.1-rel
v2.0.1-rel

View File

@ -1,8 +1,8 @@
/** ensure our userpassword has select is next to the password input */
attribute#userPassword .select2-container--bootstrap-5 .select2-selection {
div#userPassword .select2-container--bootstrap-5 .select2-selection {
font-size: inherit;
width: 9em;
border: var(--bs-gray-500) 1px solid;
border: #444054 1px solid;
background-color: #f0f0f0;
}
@ -11,7 +11,7 @@ attribute#userPassword .select2-container--bootstrap-5 .select2-selection {
border-top-right-radius: unset;
}
attribute#objectClass .input-group-end:not(input.form-control) {
div#objectClass .input-group-end:not(input.form-control) {
position: absolute;
right: 1em;
top: 0.5em;
@ -30,10 +30,7 @@ input.form-control.input-group-end {
.custom-tooltip-danger {
--bs-tooltip-bg: var(--bs-danger);
}
.custom-tooltip {
--bs-tooltip-bg: var(--bs-gray-900);
}
.tooltip {

2
public/js/custom.js vendored
View File

@ -44,10 +44,8 @@ function getNode(item) {
location.reload();
break;
case 500:
case 555: // Missing Method
$('.main-content').empty().append(e.responseText);
break;
default:
alert('Well that didnt work? Code ['+e.status+']');
}

View File

@ -7,6 +7,3 @@
// Select2
@import "select2/dist/css/select2";
@import "select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme";
// Bootstrap icons
@import "bootstrap-icons"

View File

@ -1,9 +1,9 @@
/*!
=========================================================
* ArchitectUI HTML Theme Dashboard - v4.1.0
* ArchitectUI HTML Theme Dashboard - v4.0.0
=========================================================
* Product Page: https://dashboardpack.com
* Copyright 2025 DashboardPack (https://dashboardpack.com)
* Copyright 2023 DashboardPack (https://dashboardpack.com)
* Licensed under MIT (https://github.com/DashboardPack/architectui-html-theme-free/blob/master/LICENSE)
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

View File

@ -1,22 +0,0 @@
@use "sass:math";
@if $use-lightSpeedIn == true {
@-webkit-keyframes lightSpeedIn {
0% { -webkit-transform: translateX(100%) skewX(-$base-degrees); opacity: 0; }
60% { -webkit-transform: translateX(-20%) skewX($base-degrees); opacity: 1; }
80% { -webkit-transform: translateX(0%) skewX(calc(-1 * $base-degrees / 2)); opacity: 1; }
100% { -webkit-transform: translateX(0%) skewX(0deg); opacity: 1; }
}
@keyframes lightSpeedIn {
0% { transform: translateX(100%) skewX(-$base-degrees); opacity: 0; }
60% { transform: translateX(-20%) skewX($base-degrees); opacity: 1; }
80% { transform: translateX(0%) skewX(calc(-1 * $base-degrees / 2)); opacity: 1; }
100% { transform: translateX(0%) skewX(0deg); opacity: 1; }
}
.lightSpeedIn {
@include animate-prefixer(animation-name, lightSpeedIn);
@include animate-prefixer(animation-timing-function, $base-timing-function-out);
}
}

View File

@ -58,7 +58,8 @@ $use-all: true;
"~animate-sass/animations/flippers/flipOutY";
// LIGHTSPEED
@import "./_animate-override";
@import "~animate-sass/animations/lightspeed/lightSpeedIn",
"~animate-sass/animations/lightspeed/lightSpeedOut";
// ROTATE
@import "~animate-sass/animations/rotate-enter/rotateIn",

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>{{ $server->name }}</strong></small>
<small>@lang('Sign in to') <strong>{{ config('server')->name }}</strong></small>
</h4>
</div>

View File

@ -14,7 +14,54 @@
</div>
<div class="page-title-actions">
@yield('page_actions')
<div class="row">
<div class="col">
<div class="action-buttons float-end">
<ul class="nav">
@if(isset($page_actions) && $page_actions->contains('export'))
<li>
<span data-bs-toggle="modal" data-bs-target="#entry_export-modal">
<button class="btn btn-outline-dark p-1 m-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Export')"><i class="fas fa-fw fa-download fs-5"></i></button>
</span>
</li>
@endif
@if(isset($page_actions) && $page_actions->contains('copy'))
<li>
<button class="btn btn-outline-dark p-1 m-1" id="entry-copy-move" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Copy/Move')"><i class="fas fa-fw fa-copy fs-5"></i></button>
</li>
@endif
@if((isset($page_actions) && $page_actions->contains('edit')) || old())
<li>
<button class="btn btn-outline-dark p-1 m-1" id="entry-edit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Edit Entry')"><i class="fas fa-fw fa-edit fs-5"></i></button>
</li>
@endif
<!-- @todo Dont offer the delete button for an entry with children -->
@if(isset($page_actions) && $page_actions->contains('delete'))
<li>
<span id="entry-delete" data-bs-toggle="modal" data-bs-target="#page-modal">
<button class="btn btn-outline-danger p-1 m-1" data-bs-custom-class="custom-tooltip-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Delete Entry')"><i class="fas fa-fw fa-trash-can fs-5"></i></button>
</span>
</li>
@endif
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('button[id=entry-edit]').on('click',function(item) {
item.preventDefault();
if ($(this).hasClass('btn-dark'))
return;
editmode();
});
});
</script>
@append

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">{{ $server->name }}</li>
<li class="app-sidebar__heading">{{ config('server')->name }}</li>
<li>
<i id="treeicon" class="metismenu-icon fa-fw fas fa-sitemap"></i>
<span class="f16" id="tree"></span>

View File

@ -1,9 +0,0 @@
<div class="alert alert-danger p-0" style="font-size: .80em;">
<table class="table table-borderless table-danger p-0 m-0">
<tr>
<td class="align-top" style="width: 5%;"><i class="fas fa-fw fa-2x fa-exclamation-triangle"></i></td>
<td>Unable to display this attribute as it has attribute tags [<strong>{!! $tags->join('</strong>, <strong>') !!}</strong>].<br>
You can manage it with an LDIF import.</td>
</tr>
</table>
</div>

View File

@ -15,8 +15,6 @@
</div>
</div>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag"/>
<x-attribute :o="$o" :edit="true" :new="$new ?? FALSE"/>
</div>
</div>
@yield($o->name_lc.'-scripts')

View File

@ -1,10 +1,9 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
<div class="col-12">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$new ? [NULL] : $o->tagValues($langtag)]),$langtag,[]) as $key => $value)
@if($edit && (! $o->is_rdn))
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value)
@if (($edit ?? FALSE) && ! $o->is_rdn)
<div class="input-group has-validation">
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! ($tv=$o->tagValuesOld($langtag))->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" placeholder="{{ ! is_null($x=$tv->get($loop->index)) ? $x : '['.__('NEW').']' }}" @readonly(! $new) @disabled($o->isDynamic())>
<input type="text" @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="{{ $value }}" placeholder="{{ ! is_null($x=Arr::get($o->values,$loop->index)) ? $x : '['.__('NEW').']' }}" @readonly(! ($new ?? FALSE))>
<div class="invalid-feedback pb-2">
@if($e)
@ -14,8 +13,7 @@
</div>
@else
<input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
{{ $value }}
@endif
@endforeach
</div>
</x-attribute.layout>

View File

@ -1,20 +1,20 @@
<!-- @todo We are not handling redirect backs yet with updated photos -->
<!-- $o=Binary\JpegPhoto::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
<x-attribute.layout :edit="$edit" :new="false" :o="$o">
<table class="table table-borderless p-0 m-0">
@foreach($o->tagValuesOld() as $key => $value)
@foreach (($old ? $o->old_values : $o->values) as $value)
<tr>
@switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@case('image/jpeg')
@default
<td>
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}">
<img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
<input type="hidden" name="{{ $o->name_lc }}[]" value="{{ md5($value) }}">
<img @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index))]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
@if ($edit)
<br>
<!-- @todo TO IMPLEMENT -->
<button class="btn btn-sm btn-danger deletable d-none mt-3" disabled><i class="fas fa-trash-alt"></i> @lang('Delete')</button>
<span class="btn btn-sm btn-danger deletable d-none mt-3"><i class="fas fa-trash-alt"></i> @lang('Delete')</span>
<div class="invalid-feedback pb-2">
@if($e)

View File

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

View File

@ -1,2 +0,0 @@
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.datetime')

View File

@ -1,2 +0,0 @@
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.datetime')

View File

@ -1,2 +0,0 @@
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.datetime')

View File

@ -1,2 +0,0 @@
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.input')

View File

@ -1,2 +0,0 @@
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.datetime')

View File

@ -1,19 +0,0 @@
<!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=KrbPrincipleKey::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
@foreach($o->tagValuesOld($langtag) as $key => $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.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ $o->render_item_old($langtag.'.'.$key) }}
@endif
@endforeach
</x-attribute.layout>

View File

@ -1,109 +0,0 @@
<!-- $o=KrbTicketFlags::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $key => $value)
@if($edit)
<div id="32"></div>
<div id="16"></div>
<div class="input-group has-validation mb-3">
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ $o->render_item_old($langtag.'.'.$key) }}
@endif
@endforeach
</x-attribute.layout>
@section($o->name_lc.'-scripts')
<script type="text/javascript">
var value = {{ $value ?? 0 }};
var label = {!! $helper !!};
function tooltip(bit) {
if (bit === undefined)
return;
return label[bit] ? label[bit] : 'Bit '+bit;
}
function binary(s=31,e=0) {
var result = '';
for (let x=s;x>=e;x--) {
var bit = (value&Math.pow(2,x));
result += '<i id="b'+x+'" style="margin-left:-1px;" class="fs-4 bi bi-'+(bit ? '1' : '0')+'-square'+(bit ? '-fill' : '')+'" data-bs-toggle="tooltip" data-bs-placement="bottom" title="'+tooltip(x)+'"></i>';
}
return result;
}
function krbticketflags() {
$('div#32').append(binary(31,16));
$('div#16').append(binary(15,0));
$('attribute#krbTicketFlags').find('i')
.on('click',function() {
var item = $(this);
if ($('form#dn-edit').attr('readonly'))
return;
var key = Number(item.attr('id').substring(1));
if (item.data('old') === undefined)
item.data('old',null);
item.toggleClass('text-success');
// has the item changed?
if (item.data('old') === null) {
// It was set to 1
if (item.hasClass('bi-1-square-fill')) {
item.data('old',1);
item.removeClass('bi-1-square-fill').addClass('bi-0-square-fill');
value -= Math.pow(2,key);
// It was set to 0
} else if (item.hasClass('bi-0-square')) {
item.data('old',0);
item.removeClass('bi-0-square').addClass('bi-1-square-fill');
value += Math.pow(2,key);
}
} else {
if (item.data('old') === 0) {
item.removeClass('bi-1-square-fill').addClass('bi-0-square');
value -= Math.pow(2,key);
} else {
item.removeClass('bi-0-square-fill').addClass('bi-1-square-fill');
value += Math.pow(2,key);
}
item.data('old',null);
}
$('attribute#krbTicketFlags').find('input').val(value);
});
}
// When returning to a Entry after an update, jquery hasnt loaded yet, so make sure we defer this to after the page has run
if (window.$ === undefined) {
document.addEventListener('DOMContentLoaded',() => krbticketflags());
} else {
krbticketflags();
$('attribute#krbTicketFlags').find('i')
.tooltip();
}
</script>
@endsection

View File

@ -1,12 +1,10 @@
<div class="row pt-2">
<div @class(['col-1','d-none'=>(! $edit) && (! ($detail ?? false))])></div>
<div class="col-10">
<attribute id="{{ $o->name }}">
<div @class(['col-1','d-none'=>(! $edit)])></div>
<div class="col-10 p-2">
<div id="{{ $o->name }}">
{{ $slot }}
</attribute>
</div>
<x-attribute.widget.options :o="$o" :edit="$edit" :new="$new"/>
</div>
</div>
@yield($o->name_lc.'-scripts')

View File

@ -1,12 +1,12 @@
<!-- $o=Attribute/ObjectClass::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$new ? [NULL] : $o->tagValues($langtag)]),$langtag,[]) as $key => $value)
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach(old($o->name_lc,$o->values) as $value)
@if ($edit)
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/>
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value"/>
@else
{{ $o->render_item_old($key) }}
{{ $value }}
@if ($o->isStructural($value))
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}">
<input type="hidden" name="{{ $o->name_lc }}[]" value="{{ $value }}">
<span class="float-end">@lang('structural')</span>
@endif
<br>

View File

@ -1,11 +1,10 @@
<!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=Password::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
@foreach($o->tagValuesOld($langtag) as $key => $value)
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value)
@if($edit)
<div class="input-group has-validation mb-3">
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[{{ $langtag }}][]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/>
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/>
<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)>
<div class="invalid-feedback pb-2">
@if($e)
@ -14,7 +13,7 @@
</div>
</div>
@else
{{ $o->render_item_old($langtag.'.'.$key) }}
{{ (($x=$o->hash($value)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '' }}{{ str_repeat('*',16) }}
@endif
@endforeach
</x-attribute.layout>
@ -23,7 +22,7 @@
<div class="row">
<div class="offset-1 col-4 p-2">
<span class="p-0 m-0">
<button id="entry-userpassword-check" type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
<button type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#userpassword_check-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
</span>
</div>
</div>

View File

@ -1,6 +1,6 @@
<!-- $o=RDN::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach(($o->values->count() ? $o->values : ['']) as $value)
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($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').first().attr('readonly',true);
$('#'+rdn_attr).find('input').attr('readonly',true);
set_rdn_value();
}
function set_rdn_value() {
if (rdn_attr && rdn_value_set)
$('#'+rdn_attr).find('input').first().val($('input#rdn_value').val());
$('#'+rdn_attr).find('input').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').first().attr('readonly',false).val('');
$('#'+rdn_attr).find('input').attr('readonly',false).val('');
// set RDN attribute read-only
if (rdn_attr = $(this).val())
$('#'+rdn_attr).find('input').first().attr('readonly',true).val('');
$('#'+rdn_attr).find('input').attr('readonly',true).val('');
set_rdn_value();
})

View File

@ -1 +0,0 @@
{!! $o->values->join('<br>') !!}

View File

@ -1,9 +1,9 @@
<span id="objectclass_{{$value}}">
<div class="input-group has-validation">
<!-- @todo Have an "x" to remove the entry, we need an event to process the removal, removing any attribute values along the way -->
<input type="text" @class(['form-control','input-group-end','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)>
<input type="text" @class(['form-control','input-group-end','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)>
@if ($o->isStructural($value))
<span class="input-group-end text-black-50">@lang('structural')</span>
<span class="input-group-end text-black-50">structural</span>
@else
<span class="input-group-end"><i class="fas fa-fw fa-xmark"></i></span>
@endif

View File

@ -1,31 +1,16 @@
@use(App\Classes\LDAP\Attribute\Certificate)
@use(App\Classes\LDAP\Attribute\CertificateList)
@use(App\Classes\LDAP\Attribute\Binary\JpegPhoto)
@use(App\Classes\LDAP\Attribute\ObjectClass)
@php($clone=FALSE)
<span class="p-0 m-0">
@if($o->is_rdn)
<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"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
@elseif($edit && $o->can_addvalues)
<span class="p-0 m-0">
@switch(get_class($o))
@case(Certificate::class)
@case(CertificateList::class)
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-replace" disabled><i class="fas fa-fw fa-certificate"></i> @lang('Replace')</span>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#{{ $o->name }}-replace.addable').click(function(e) {
alert('Sorry, not implemented yet');
e.preventDefault();
return false;
});
});
</script>
@append
@case('App\Classes\LDAP\Attribute\Binary\JpegPhoto')
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span>
@break
@case(ObjectClass::class)
<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>
@case('App\Classes\LDAP\Attribute\ObjectClass')
<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>
<!-- 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">
@ -52,20 +37,47 @@
$(document).ready(function() {
var added_oc = []; // Object classes being added to this entry
var rendered = false;
var newadded = [];
if (newadded.length)
process_oc();
// Show our ObjectClass modal so that we can add more objectclasses
$('#new_objectclass-modal').on('shown.bs.modal',function() {
if (! rendered)
$.ajax({
method: 'POST',
url: '{{ url('entry/objectclass/add') }}',
data: {
oc: oc,
},
cache: false,
success: function(data) {
$('select#newoc').select2({
dropdownParent: $('#new_objectclass-modal'),
theme: 'bootstrap-5',
multiple: true,
data: data,
});
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
});
rendered = true;
})
// When the ObjectClass modal is closed, process what was selected
$('#new_objectclass-modal').on('hide.bs.modal',function() {
var newadded = $('select#newoc').val();
// If nothing selected, we dont have anything to do
if (added_oc.sort().join('|') === newadded.sort().join('|'))
return;
function process_oc() {
// Find out what was selected, and add them
newadded.forEach(function (item) {
if (added_oc.indexOf(item) !== -1)
return;
// Add our new OC to the list of OCs
oc.push(item);
// Add attribute to the page
$.ajax({
method: 'POST',
@ -85,7 +97,6 @@
},
});
// Get a list of attributes already on the page, so we dont double up
$.ajax({
method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
@ -94,9 +105,6 @@
// Render any must attributes
if (data.must.length) {
data.must.forEach(function(item) {
if ($('attribute#'+item).length)
return;
// Add attribute to the page
$.ajax({
method: 'POST',
@ -188,68 +196,14 @@
});
added_oc = newadded;
}
// Show our ObjectClass modal so that we can add more objectclasses
$('#new_objectclass-modal').on('shown.bs.modal',function() {
if (! rendered)
$.ajax({
method: 'POST',
url: '{{ url('entry/objectclass/add') }}',
data: {
oc: oc,
},
cache: false,
success: function(data) {
$('select#newoc').select2({
dropdownParent: $('#new_objectclass-modal'),
theme: 'bootstrap-5',
multiple: true,
data: data,
});
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
});
rendered = true;
})
// When the ObjectClass modal is closed, process what was selected
$('#new_objectclass-modal').on('hide.bs.modal',function() {
newadded = $('select#newoc').val();
// If nothing selected, we dont have anything to do
if (added_oc.sort().join('|') === newadded.sort().join('|'))
return;
process_oc();
});
});
</script>
@append
@break
@case(JpegPhoto::class)
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-upload" disabled><i class="fas fa-fw fa-file-arrow-up"></i> @lang('Upload JpegPhoto')</span>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
$('#{{ $o->name }}-upload.addable').click(function(e) {
alert('Sorry, not implemented yet');
e.preventDefault();
return false;
});
});
</script>
@append
@break
<!-- All other attributes -->
@case('App\Classes\LDAP\Attribute')
@default
@if($o->isDynamic()) @break @endif
@php($clone=TRUE)
<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>
@ -260,16 +214,13 @@
// Create a new entry when Add Value clicked
$('#{{ $o->name }}-addnew.addable').click(function (item) {
var cln = $(this).parent().parent().find('input:last').parent().clone();
cln.find('input:last')
.attr('value','')
.attr('placeholder', '[@lang('NEW')]')
.addClass('border-focus')
.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
cln.find('input:last').attr('value','').attr('placeholder', '[@lang('NEW')]');
cln.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
});
});
</script>
@endif
@append
@endswitch
@endif
</span>
@endif

View File

@ -1,8 +0,0 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :o="$o" :detail="true">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
<div class="input-group">
<input type="text" class="form-control mb-1" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" disabled>
</div>
@endforeach
</x-attribute.layout>

View File

@ -1,8 +0,0 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :o="$o" :detail="true">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
<div class="input-group">
<input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
</div>
@endforeach
</x-attribute.layout>

View File

@ -66,7 +66,7 @@
@endif
@isset($options)
@if(($autoselect ?? FALSE) && $options->count() === 1)
@if($options->count() === 1)
$('#{{ $id ?? $name }}')
.val('{{ $options->first()['id'] }}')
.trigger("change")

View File

@ -1,28 +0,0 @@
@use(App\Classes\LDAP\Attribute\Certificate)
<!-- $o=Certificate::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" langtag="binary">
@foreach($o->tagValuesOld('binary') as $key => $value)
<!-- If this attribute is not handle, it'll be an Attribute::class, we'll just render it normally -->
@if(($o instanceof Certificate) && $edit)
<input type="hidden" name="name={{ $o->name_lc }}[binary][]" value="{{ md5($value) }}">
<div class="input-group has-validation mb-3">
<textarea class="form-control mb-1 font-monospace" rows="{{ count(explode("\n",$x=$o->certificate())) }}" style="overflow: hidden; font-size: 90%;" disabled>{{ $x }}</textarea>
<div class="invalid-feedback pb-2">
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
{{ join('|',$e) }}
@endif
</div>
</div>
<div class="input-helper">
@lang('Certificate Subject'): <strong>{{ $o->subject($loop->index) }}</strong><br/>
{{ ($expire=$o->expires($loop->index))->isPast() ? __('Expired') : __('Expires') }}: <strong>{{ $expire->format(config('pla.datetime_format','Y-m-d H:i:s')) }}</strong>
</div>
@else
<span class="form-control mb-1"><pre class="m-0">{{ $o->render_item_old('binary.'.$key) }}</pre></span>
@endif
@endforeach
</x-attribute.layout>

View File

@ -1,7 +0,0 @@
<!-- $o=CertificateList::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" langtag="binary">
@foreach($o->tagValuesOld('binary') as $key => $value)
<!-- If this attribute is not handle, it'll be an Attribute::class, we'll just render it normally -->
<span class="form-control mb-1"><pre class="m-0">{{ $o->render_item_old('binary.'.$key) }}</pre></span>
@endforeach
</x-attribute.layout>

View File

@ -5,7 +5,7 @@
<p>{{ __('Entry updated') }}</p>
<ul style="list-style-type: square;">
@foreach (session()->pull('updated') as $key => $o)
<li><abbr title="{{ $o->description }}">{{ $o->name }}</abbr>: {{ $o->values->dot()->filter()->join(',') }}</li>
<li><abbr title="{{ $o->description }}">{{ $o->name }}</abbr>: {{ $o->values->map(fn($item,$key)=>$o->render_item_new($key))->join(',') }}</li>
@endforeach
</ul>
</div>

View File

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

View File

@ -13,9 +13,10 @@
<div class="row">
<div class="col-12 pt-2">
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->unique('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name_lc])"/>
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name_lc])"/>
</div>
</div>
</div>
</div>
<div class="col-2"></div>
</div>

View File

@ -26,12 +26,6 @@
<x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/>
</th>
</tr>
@if($langtags->count())
<tr class="mt-1">
<td class="p-0 pe-2">Tags</td>
<th class="p-0">{{ $langtags->join(', ') }}</th>
</tr>
@endif
</table>
</td>
</tr>

View File

@ -1,4 +1,5 @@
<div class="row">
<div class="col-12 col-xl-3">
<select id="attributetype" class="form-control">
<option value="-all-">-all-</option>
@ -105,24 +106,6 @@
@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

@ -2,12 +2,9 @@
<div class="col-12 col-xl-3">
<select id="objectclass" class="form-control">
<option value="-all-">-all-</option>
@foreach($objectclasses->groupBy(fn($item)=>$item->isStructural()) as $oo)
<optgroup label="{{ __($oo->first()->isStructural() ? 'Structural' : 'Auxillary') }} Object Class"></optgroup>
@foreach($oo as $o)
@foreach ($objectclasses as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
@endforeach
</select>
</div>

View File

@ -1,12 +1,7 @@
@use(App\Ldap\Entry)
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header',[
'o'=>($oo=$server->fetch(old('container',$container))),
'langtags'=>collect(),
])
@include('fragment.dn.header',['o'=>($oo=config('server')->fetch(old('container',$container)))])
@endsection
@section('main-content')
@ -33,10 +28,9 @@
<div class="col-12 col-md-6">
<x-form.select
id="objectclass"
name="objectclass[{{ Entry::TAG_NOTAG }}][]"
old="objectclass.{{ Entry::TAG_NOTAG }}"
name="objectclass[]"
:label="__('Select a Structural ObjectClass...')"
:options="($oc=$server->schema('objectclasses'))
:options="($oc=config('server')->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural())
->sortBy(fn($item)=>$item->name_lc)
->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"
@ -72,8 +66,8 @@
@endsection
@section('page-scripts')
<script type="text/javascript">
var oc = {!! $oo->getObject('objectclass')->values !!};
var rdn_attr;
function editmode() {
@ -91,28 +85,20 @@
});
// Our password type
$('attribute#userPassword .form-select').each(function() {
$('div#userPassword .form-select').each(function() {
$(this).prop('disabled',false);
})
$('.row.d-none').removeClass('d-none');
$('span.addable.d-none').removeClass('d-none');
$('span.deletable.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none');
$('#newattr-select.d-none').removeClass('d-none');
}
$(document).ready(function() {
@if($step === 2)
var oc = {!! $o->getObject('objectclass')->values !!};
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false,
beforeSend: function() {},
success: function(data) {
$('#newattrs').append(data);
@ -121,6 +107,11 @@
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false
});
// Remove the option from the list
@ -130,7 +121,6 @@
if ($(this).find("option").length === 1)
$('#newattr-select').remove();
});
@endif
editmode();
});

View File

@ -1,70 +1,7 @@
@use(App\Ldap\Entry)
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header',[
'o'=>($o ?? $o=$server->fetch($dn)),
'langtags'=>($langtags=$o->getLangTags()
->flatMap(fn($item)=>$item->values())
->unique()
->sort())
])
@endsection
@section('page_actions')
<div class="row">
<div class="col">
<div class="action-buttons float-end">
<ul class="nav">
@if(isset($page_actions) && $page_actions->get('create'))
<li>
<button class="btn btn-outline-dark p-1 m-1" id="entry-create" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Create Child Entry')"><i class="fas fa-fw fa-diagram-project fs-5"></i></button>
</li>
@endif
@if(isset($page_actions) && $page_actions->get('export'))
<li>
<span id="entry-export" data-bs-toggle="modal" data-bs-target="#page-modal">
<button class="btn btn-outline-dark p-1 m-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Export')"><i class="fas fa-fw fa-download fs-5"></i></button>
</span>
</li>
@endif
@if(isset($page_actions) && $page_actions->get('copy'))
<li>
<button class="btn btn-outline-dark p-1 m-1" id="entry-copy-move" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Copy/Move')" disabled><i class="fas fa-fw fa-copy fs-5"></i></button>
</li>
@endif
@if(isset($page_actions) && $page_actions->get('edit'))
<li>
<button class="btn btn-outline-dark p-1 m-1" id="entry-edit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Edit Entry')"><i class="fas fa-fw fa-edit fs-5"></i></button>
</li>
@endif
<!-- @todo Dont offer the delete button for an entry with children -->
@if(isset($page_actions) && $page_actions->get('delete'))
<li>
<span id="entry-delete" data-bs-toggle="modal" data-bs-target="#page-modal">
<button class="btn btn-outline-danger p-1 m-1" data-bs-custom-class="custom-tooltip-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Delete Entry')"><i class="fas fa-fw fa-trash-can fs-5"></i></button>
</span>
</li>
@endif
</ul>
</div>
</div>
</div>
<div class="row">
<div class="col">
@if(($x=$o->getOtherTags()->filter(fn($item)=>$item->diff(['binary'])->count()))->count())
<div class="ms-4 mt-4 alert alert-danger p-2" style="max-width: 30em; font-size: 0.80em;">
This entry has [<strong>{!! $x->flatten()->join('</strong>, <strong>') !!}</strong>] tags used by [<strong>{!! $x->keys()->join('</strong>, <strong>') !!}</strong>] that cant be managed by PLA. You can though manage those tags with an LDIF import.
</div>
@elseif(($x=$o->getLangMultiTags())->count())
<div class="ms-4 mt-4 alert alert-danger p-2" style="max-width: 30em; font-size: 0.80em;">
This entry has multi-language tags used by [<strong>{!! $x->keys()->join('</strong>, <strong>') !!}</strong>] that cant be managed by PLA. You can though manage those lang tags with an LDIF import.
</div>
@endif
</div>
</div>
@include('fragment.dn.header',['o'=>($o=config('server')->fetch($dn))])
@endsection
@section('main-content')
@ -75,69 +12,25 @@
<div class="main-card mb-3 card">
<div class="card-body">
<div class="card-header-tabs">
<ul class="nav nav-tabs mb-0">
<ul class="nav nav-tabs">
<li class="nav-item"><a data-bs-toggle="tab" href="#attributes" class="nav-link active">@lang('Attributes')</a></li>
<li class="nav-item"><a data-bs-toggle="tab" href="#internal" class="nav-link">@lang('Internal')</a></li>
@env(['local'])
<li class="nav-item"><a data-bs-toggle="tab" href="#debug" class="nav-link">@lang('Debug')</a></li>
@endenv
</ul>
<div class="tab-content">
<!-- All Attributes -->
<div class="tab-pane active" id="attributes" role="tabpanel">
<form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate readonly>
<form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate>
@csrf
<input type="hidden" name="dn" value="">
<div class="card-header border-bottom-0">
<div class="btn-actions-pane-right">
<div role="group" class="btn-group-sm nav btn-group">
@foreach($langtags->prepend(Entry::TAG_NOTAG)->push('+') as $tag)
<a data-bs-toggle="tab" href="#tab-lang-{{ $tag ?: '_default' }}" class="btn btn-outline-light border-dark-subtle @if(! $loop->index) active @endif @if($loop->last)ndisabled @endif">
@switch($tag)
@case(Entry::TAG_NOTAG)
<i class="fas fa-fw fa-border-none" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="@lang('No Lang Tag')"></i>
@break
@case('+')
<!-- @todo To implement -->
<i class="fas fa-fw fa-plus text-dark" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="@lang('Add Lang Tag')"></i>
@break
@default
<span class="f16" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" title="{{ strtoupper($tag) }}"><i class="flag {{ $tag }}"></i></span>
@endswitch
</a>
@foreach ($o->getVisibleAttributes() as $ao)
<x-attribute-type :edit="true" :o="$ao"/>
@endforeach
</div>
</div>
</div>
<div class="card-body">
<div class="tab-content">
@foreach($langtags as $tag)
<div class="tab-pane @if(! $loop->index) active @endif" id="tab-lang-{{ $tag ?: '_default' }}" role="tabpanel">
@switch($tag)
@case(Entry::TAG_NOTAG)
@foreach ($o->getVisibleAttributes($tag) as $ao)
<x-attribute-type :edit="true" :o="$ao" :langtag="$tag"/>
@endforeach
@break
@case('+')
<div class="ms-auto mt-4 alert alert-warning p-2" style="max-width: 30em; font-size: 0.80em;">
It is not possible to create new language tags at the moment. This functionality should come soon.<br>
You can create them with an LDIF import though.
</div>
@break
@default
@foreach ($o->getVisibleAttributes($langtag=sprintf('lang-%s',$tag)) as $ao)
<x-attribute-type :edit="true" :o="$ao" :langtag="$langtag"/>
@endforeach
@endswitch
</div>
@endforeach
</div>
</div>
@include('fragment.dn.add_attr')
</form>
@ -151,11 +44,26 @@
</div>
<!-- Internal Attributes -->
<div class="tab-pane mt-3" id="internal" role="tabpanel">
<div class="tab-pane" id="internal" role="tabpanel">
@foreach ($o->getInternalAttributes() as $ao)
<x-attribute-type :o="$ao"/>
@endforeach
</div>
<!-- Debug -->
<div class="tab-pane" id="debug" role="tabpanel">
<div class="row">
<div class="col-4">
@dump($o)
</div>
<div class="col-4">
@dump($o->getAttributes())
</div>
<div class="col-4">
@dump(['available'=>$o->getAvailableAttributes()->pluck('name'),'missing'=>$o->getMissingAttributes()->pluck('name')])
</div>
</div>
</div>
</div>
</div>
</div>
@ -169,6 +77,63 @@
<div class="modal-content"></div>
</div>
</div>
<!-- EXPORT -->
<div class="modal fade" id="entry_export-modal" tabindex="-1" aria-labelledby="entry_export-label" aria-hidden="true">
<div class="modal-dialog modal-lg modal-fullscreen-lg-down">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="entry_export-label">LDIF for {{ $dn }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="entry_export"><div class="fa-3x"><i class="fas fa-spinner fa-pulse fa-sm"></i></div></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-sm btn-primary" id="entry_export-download">Download</button>
</div>
</div>
</div>
</div>
@if($up=$o->getObject('userpassword'))
<!-- CHECK USERPASSWORD -->
<div class="modal fade" id="userpassword_check-modal" tabindex="-1" aria-labelledby="userpassword_check-label" aria-hidden="true">
<div class="modal-dialog modal-lg modal-fullscreen-lg-down">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="userpassword_check-label">Check Passwords for {{ $dn }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table class="table table-bordered p-1">
@foreach($up->values as $key => $value)
<tr>
<th>Check</th>
<td>{{ $up->render_item_old($key) }}</td>
<td>
<input type="password" style="width: 90%" name="password[{{$key}}]"> <i class="fas fa-fw fa-lock"></i>
<div class="invalid-feedback pb-2">
Invalid Password
</div>
</td>
</tr>
@endforeach
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-sm btn-primary" id="userpassword_check-submit"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Check</button>
</div>
</div>
</div>
</div>
@endif
@endsection
@section('page-scripts')
@ -176,15 +141,24 @@
var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function download(filename,text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function editmode() {
$('#dn-edit input[name="dn"]').val(dn);
$('form#dn-edit').attr('readonly',false);
$('button[id=entry-edit]')
.removeClass('btn-outline-dark')
.addClass('btn-dark')
.addClass('opacity-100')
.attr('disabled',true);
.addClass('btn-dark');
// Find all input items and turn off readonly
$('input.form-control').each(function() {
@ -196,13 +170,13 @@
});
// Our password type
$('attribute#userPassword .form-select').each(function() {
$('div#userPassword .form-select').each(function() {
$(this).prop('disabled',false);
})
$('.row.d-none').removeClass('d-none');
$('span.addable.d-none').removeClass('d-none');
$('span.deletable.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none');
@if($o->getMissingAttributes()->count())
$('#newattr-select.d-none').removeClass('d-none');
@ -210,20 +184,6 @@
}
$(document).ready(function() {
$('button[id=entry-create]').on('click',function(item) {
location.replace('/#{{ Crypt::encryptString(sprintf('*%s|%s','create',$dn)) }}');
location.reload();
});
$('button[id=entry-edit]').on('click',function(item) {
item.preventDefault();
if ($(this).hasClass('btn-dark'))
return;
editmode();
});
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
@ -232,7 +192,7 @@
$('#newattrs').append(data);
},
error: function(e) {
if (e.status !== 412)
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add') }}/'+item.target.value,
@ -250,6 +210,13 @@
$('#newattr-select').remove();
});
$('#entry_export-download').on('click',function(item) {
item.preventDefault();
let ldif = $('#entry_export').find('pre:first'); // update this selector in your local version
download('ldap-export.ldif',ldif.html());
});
$('#page-modal').on('shown.bs.modal',function(item) {
var that = $(this).find('.modal-content');
@ -267,63 +234,7 @@
that.empty().html(data);
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
break;
case 'entry-export':
$.ajax({
method: 'GET',
url: '{{ url('modal/export') }}/'+dn,
dataType: 'html',
cache: false,
beforeSend: function() {
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
},
success: function(data) {
that.empty().html(data);
that = $('#entry_export');
$.ajax({
method: 'GET',
url: '{{ url('entry/export') }}/'+dn,
cache: false,
beforeSend: function() {
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
},
success: function(data) {
that.empty().append(data);
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
break;
case 'entry-userpassword-check':
$.ajax({
method: 'GET',
url: '{{ url('modal/userpassword-check') }}/'+dn,
dataType: 'html',
cache: false,
beforeSend: function() {
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
},
success: function(data) {
that.empty().html(data);
},
error: function(e) {
if (e.status !== 412)
if (e.status != 412)
alert('That didnt work? Please try again....');
},
})
@ -334,6 +245,78 @@
}
});
$('#entry_export-modal').on('shown.bs.modal',function() {
$.ajax({
type: 'GET',
success: function(data) {
$('#entry_export').empty().append(data);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/export') }}/'+dn,
cache: false
})
})
@if($up)
$('button[id=userpassword_check-submit]').on('click',function(item) {
var that = $(this);
var passwords = $('#userpassword_check-modal')
.find('input[name^="password["')
.map((key,item)=>item.value);
if (passwords.length === 0) return false;
$.ajax({
type: 'POST',
beforeSend: function() {
// Disable submit, add spinning icon
that.prop('disabled',true);
that.find('i').removeClass('d-none');
},
complete: function() {
that.prop('disabled',false);
that.find('i').addClass('d-none');
},
success: function(data) {
data.forEach(function(item,key) {
var i = $('#userpassword_check-modal')
.find('input[name="password['+key+']')
.siblings('i');
var feedback = $('#userpassword_check-modal')
.find('input[name="password['+key+']')
.siblings('div.invalid-feedback');
if (item === 'OK') {
i.removeClass('text-danger').addClass('text-success').removeClass('fa-lock').addClass('fa-lock-open');
if (feedback.is(':visible'))
feedback.hide();
} else {
i.removeClass('text-success').addClass('text-danger').removeClass('fa-lock-open').addClass('fa-lock');
if (! feedback.is(':visible'))
feedback.show();
}
})
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/password/check') }}',
data: {
dn: dn,
password: Array.from(passwords),
},
dataType: 'json',
cache: false
})
});
@endif
@if(old())
editmode();
@endif

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-2"><strong>@lang('LDIF Import')</strong><br><small>@lang('To Server') <strong>{{ $server->name }}</strong></small></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>
</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>{{ $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>{{ config('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-2"><strong>@lang('Server Info')</strong><br><small>{{ $server->rootDSE()->entryuuid[0] ?? '' }}</small></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>
</tr>
</table>
@endsection
@ -13,10 +13,10 @@
<div class="main-card mb-3 card">
<div class="card-body">
<table class="table">
@foreach ($server->rootDSE()->getObjects() as $attribute => $ao)
@foreach ($s->rootDSE()->getObjects() as $attribute => $ao)
<tr>
<th class="w-25">
{!! ($x=$server->schema('attributetypes',$attribute))
{!! ($x=$s->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-2"><strong>{{ Server::schemaDN() }}</strong></td>
<td class="top text-end align-text-top p-0 pt-2"><strong>{{ Server::schemaDN() }}</strong></td>
</tr>
</table>
@endsection

View File

@ -1,5 +1,5 @@
<div class="modal-header bg-danger text-white">
<h1 class="modal-title fs-5">
<h1 class="modal-title fs-5" id="entry_export-label">
<i class="fas fa-fw fa-exclamation-triangle"></i> <strong>@lang('WARNING')</strong>: @lang('Delete') <strong>{{ Crypt::decryptString($dn) }}</strong>
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>

View File

@ -1,37 +0,0 @@
<div class="modal-header bg-dark text-white">
<h1 class="modal-title fs-5">
LDIF for {{ Crypt::decryptString($dn) }}
</h1>
</div>
<div class="modal-body">
<div id="entry_export"></div>
</div>
<div class="modal-footer">
<x-modal.close/>
<button id="entry_export-download" type="button" class="btn btn-sm btn-primary">@lang('Download')</button>
</div>
<script type="text/javascript">
function download(filename,text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
$(document).ready(function() {
$('button[id=entry_export-download]').on('click',function(item) {
item.preventDefault();
let ldif = $('#entry_export').find('pre:first');
download('ldap-export.ldif',ldif.html());
});
});
</script>

View File

@ -1,86 +0,0 @@
@php
$o = $server->fetch(Crypt::decryptString($dn))
@endphp
<div class="modal-header bg-dark text-white">
<h1 class="modal-title fs-5">Check Passwords for {{ $o->getDN() }}</h1>
</div>
<div class="modal-body">
<table class="table table-bordered p-1">
@foreach(($up=$o->getObject('userpassword'))->values->dot() as $dotkey => $value)
<tr>
<th>Check</th>
<td>{{ $up->render_item_old($dotkey) }}</td>
<td>
<input type="password" style="width: 90%" name="password[{{ $dotkey }}]"> <i class="fas fa-fw fa-lock"></i>
<div class="invalid-feedback pb-2">
@lang('Invalid Password')
</div>
</td>
</tr>
@endforeach
</table>
</div>
<div class="modal-footer">
<x-modal.close/>
<button id="userpassword_check-submit" type="button" class="btn btn-sm btn-primary"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> @lang('Check')</button>
</div>
<script type="text/javascript">
$('button[id=userpassword_check-submit]').on('click',function(item) {
var that = $(this);
var passwords = $('#page-modal')
.find('input[name^="password["')
.map((key,item)=>item.value);
if (passwords.length === 0) return false;
$.ajax({
method: 'POST',
url: '{{ url('entry/password/check') }}',
data: {
dn: dn,
password: Array.from(passwords),
},
dataType: 'json',
cache: false,
beforeSend: function() {
// Disable submit, add spinning icon
that.prop('disabled',true);
that.find('i').removeClass('d-none');
},
complete: function() {
that.prop('disabled',false);
that.find('i').addClass('d-none');
},
success: function(data) {
data.forEach(function(item,key) {
var i = $('#page-modal')
.find('input[name="password['+key+']')
.siblings('i');
var feedback = $('#page-modal')
.find('input[name="password['+key+']')
.siblings('div.invalid-feedback');
if (item === 'OK') {
i.removeClass('text-danger').addClass('text-success').removeClass('fa-lock').addClass('fa-lock-open');
if (feedback.is(':visible'))
feedback.hide();
} else {
i.removeClass('text-success').addClass('text-danger').removeClass('fa-lock-open').addClass('fa-lock');
if (! feedback.is(':visible'))
feedback.show();
}
})
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
});
</script>

View File

@ -1,12 +1,7 @@
@extends('home')
@section('page_title')
@include('fragment.dn.header',[
'langtags'=>($langtags=$o->getLangTags()
->flatMap(fn($item)=>$item->values())
->unique()
->sort())
])
@include('fragment.dn.header')
@endsection
@section('main-content')
@ -28,35 +23,25 @@
<thead>
<tr>
<th>Attribute</th>
<th>Tag</th>
<th>OLD</th>
<th>NEW</th>
</tr>
</thead>
<tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
@foreach ($o->getAttributesAsObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr>
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}">
<th rowspan="{{ $x=max($oo->values->keys()->max(),$oo->old_values->keys()->max())+1}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th>
@foreach($oo->values->dot()->keys()->merge($oo->values_old->dot()->keys())->unique() as $dotkey)
@if($loop->index)
@for($xx=0;$xx<$x;$xx++)
@if($xx)
</tr><tr>
@endif
<th>
{{ $dotkey }}
</th>
@if((! Arr::get($oo->values_old->dot(),$dotkey)) && (! Arr::get($oo->values->dot(),$dotkey)))
<td colspan="2" class="text-center">@lang('Ignoring blank value')</td>
@else
<td>{{ (($r=$oo->render_item_old($dotkey)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo,$dotkey) }}"></td>
@endif
@endforeach
<td>{{ $oo->render_item_old($xx) ?: '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ $oo->render_item_new($xx) ?: '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[]" value="{{ Arr::get($oo->values,$xx) }}"></td>
@endfor
</tr>
@endforeach
</tbody>

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::view('info','frames.info');
Route::view('debug','debug');
Route::get('info','info');
Route::get('debug','debug');
Route::post('frame','frame');
Route::view('import','frames.import');
Route::get('import','import_frame');
Route::get('schema','schema_frame');
Route::group(['prefix'=>'user'],function() {
@ -54,7 +54,5 @@ Route::controller(HomeController::class)->group(function() {
Route::post('import/process/{type}','import');
Route::view('modal/delete/{dn}','modals.entry-delete');
Route::view('modal/export/{dn}','modals.entry-export');
Route::view('modal/userpassword-check/{dn}','modals.entry-userpassword-check');
});
});

Some files were not shown because too many files have changed in this diff Show More