Compare commits

...

37 Commits

Author SHA1 Message Date
a61f5e9b97 Release v2.1.0
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-04-11 15:38:06 +10:00
d845d87a6e Laravel framework and javascript modules update 2025-04-11 15:38:06 +10:00
b501dfe824 During create we were passing the wrong objectlcasses to the ajax call when adding a new attribute. 2025-04-11 15:38:06 +10:00
3fad9770a3 When submitting an import form and validation fails, there is no DN returned, so dont update one. 2025-04-11 14:59:24 +10:00
b1d153aa9f Change Attribute/UserCertificate into Syntax/Certificate for any Certificate attributes. Add Syntax/CertificateList.
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-11 08:55:32 +10:00
8b0af505a1 When viewing the schema, highlight Structural and non-Structural classes
Some checks failed
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
2025-04-11 08:55:32 +10:00
f0eaff7d42 Removing debugging that made it into LDIF import 2025-04-11 08:55:32 +10:00
352bbe2b75 Capture PLA version when submitting a bug report 2025-04-11 08:50:31 +10:00
0fe4894192 Create config.yml to disable blank issues reporting 2025-04-11 08:43:37 +10:00
a7be4e00b4 Fix rendering new attributes, so that they dont render as dynamic. Fix adding new objectClasses to entries, need langtag to render the component
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 28s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m28s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-08 22:04:48 +10:00
2abc321eca Fix for showing no_lang_tag attrs (which are displayed without values) on a lang_tag attr pane when viewing a DN 2025-04-08 14:50:23 +10:00
6b2fb8dee4 Dont add hints for internal attributes. Our hints now also returns a collection. 2025-04-08 11:04:31 +10:00
66537dcec8 Revert version to 2.1.0-dev
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-07 22:35:08 +10:00
1bf8830887 When rendering dynamic attributes, dont make them editable. Closes #10 and #89.
Also some minor fixes when returning from a post for a DN with attribute tags.
2025-04-07 22:35:08 +10:00
c4d28c8a23 Add support for displaying user certificates, that are recorded in the directory with a ;binary tag. Closes #75
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 28s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-07 14:34:56 +10:00
29c460fd4b Ensure our validation message is shown when not selecting an objectclass when creating a new entry 2025-04-07 14:34:56 +10:00
3196b10aed Add OID description for searchguide attributes 2025-04-07 14:34:56 +10:00
f41b484dc4 More ldap configuration settings for demo ldap environment.
Should help when working on #10, #89, #287.
2025-04-06 22:50:46 +10:00
855d7ae75c Move entry-edit javascript out of architect theme 2025-04-06 22:50:46 +10:00
ffa8cdc826 Fix User Password Check now that we have attribute tags 2025-04-06 22:50:46 +10:00
8f39603f9f Improved determination of attribute object being dirty, improved detection of blank input and processing
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-06 19:33:04 +10:00
bcea6de791 Validation of inputs for a DN with language tags - work for #16
Some checks failed
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
2025-04-06 13:54:32 +10:00
28f4869628 Attribute is no longer iterable - cant be used now that we manage attribute tags 2025-04-06 13:54:32 +10:00
cf535286c5 Render HTML inputs for a DN with language tags - work for #16 2025-04-06 13:54:32 +10:00
633513d3e9 Display a DN entry with language tags - work for #16 2025-04-06 13:54:32 +10:00
705bfb2d64 Update page_actions to be consistent with what we can do so far 2025-04-05 23:24:45 +11:00
3a3bf2addb Make select automatically selecting one item when there is only one configurable 2025-04-05 23:24:45 +11:00
5bb573100b Further to eab4f04 we need some attributes to render tree icons 2025-04-04 20:48:42 +11:00
a57ee78492 Ensure that Attribute::required() doesnt work with NULL $this->schema. Avoids issue as reported by #306 2025-04-04 20:48:42 +11:00
eab4f0427c No need to retrieve all records by default when getting children. By default sort records by DN until we implemented configurable sorting.
Should help the timeout issues reported in #301
2025-03-20 21:17:28 +11:00
fd2c5d1286 Add some attribute tags messages when we cant handle some attributes. 2025-03-19 09:41:47 +11:00
b35b44b2b8 Import and Export work with attribute tags 2025-03-19 09:41:47 +11:00
ce66dcb2b5 Remove deprecated Attribute::lang_tags 2025-03-19 09:41:47 +11:00
56a91f853c Fix export to work with no_attr_tags 2025-03-19 09:41:47 +11:00
81e0e58650 Handle no attribute tags at an Attribute::class level, added form/disabled components 2025-03-19 09:41:47 +11:00
1470170928 Internal attributes are now handled by the new backend setup for attribute tags 2025-03-19 09:41:47 +11:00
85c7132b30 Start of work to handle attribute tags - should help with #75 and #16 2025-03-19 09:41:47 +11:00
80 changed files with 1351 additions and 732 deletions

View File

@ -10,6 +10,9 @@ 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 '...'

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

View File

@ -61,6 +61,7 @@ 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,39 +7,39 @@ 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, \Iterator
class Attribute implements \Countable, \ArrayAccess
{
// Attribute Name
protected string $name;
private int $counter = 0;
// Is this attribute an internal attribute
protected(set) bool $is_internal = FALSE;
protected(set) bool $no_attr_tags = FALSE;
// MIN/MAX number of values
protected(set) int $min_values_count = 0;
protected(set) int $max_values_count = 0;
// RFC3866 Language Tags
/* @deprecated use $values/$values_old when playing with language tags */
protected Collection $lang_tags;
// The schema's representation of this attribute
protected(set) ?AttributeType $schema;
// The DN this object is in
protected(set) string $dn;
// The old values for this attribute - helps with isDirty() to determine if there is an update pending
protected(set) Collection $values_old;
private Collection $_values_old;
// Current Values
public Collection $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';
/*
# Has the attribute been modified
protected $modified = false;
@ -103,11 +103,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
{
$this->dn = $dn;
$this->name = $name;
$this->values_old = collect($values);
$this->_values = collect($values);
$this->_values_old = collect($values);
$this->values = collect();
$this->oc = collect($oc);
$this->lang_tags = collect();
$this->schema = (new Server)
->schema('attributetypes',$name);
@ -127,6 +126,11 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/
}
public function __call(string $name,array $arguments)
{
abort(555,'Method not handled: '.$name);
}
public function __get(string $key): mixed
{
return match ($key) {
@ -152,59 +156,51 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
'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),
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
$this->_values = $values;
break;
case 'values_old':
$this->_values_old = $values;
break;
default:
throw new \Exception('Unknown key:'.$key);
}
}
public function __toString(): string
{
return $this->name;
}
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;
}
/* INTERFACE */
public function count(): int
{
return $this->values->count();
return $this->_values->dot()->count();
}
public function offsetExists(mixed $offset): bool
{
return ! is_null($this->values->has($offset));
return $this->_values->dot()->has($offset);
}
public function offsetGet(mixed $offset): mixed
{
return $this->values->get($offset);
return $this->_values->dot()->get($offset);
}
public function offsetSet(mixed $offset, mixed $value): void
@ -217,15 +213,36 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// 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 array
* @return Collection
*/
public function hints(): array
public function hints(): Collection
{
$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'));
@ -236,11 +253,11 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
if ($this->required()->count())
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', ')));
// This attribute has language tags
if ($this->lang_tags->count())
$result->put(__('language tags'),sprintf('%s: %d',__('This Attribute has Language Tags'),$this->lang_tags->count()));
// If this attribute is a dynamic attribute
if ($this->isDynamic())
$result->put(__('dynamic'),__('These are dynamic values present as a result of another attribute'));
return $result->toArray();
return $result;
}
/**
@ -250,8 +267,22 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/
public function isDirty(): bool
{
return ($this->values_old->count() !== $this->values->count())
|| ($this->values->diff($this->values_old)->count() !== 0);
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);
}
/**
* Are these values as a result of a dynamic attribute
*
* @return bool
*/
public function isDynamic(): bool
{
return $this->schema->used_in_object_classes
->keys()
->intersect($this->schema->heirachy($this->oc))
->count() === 0;
}
/**
@ -280,9 +311,14 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
$view = view()->exists($x='components.attribute.'.$this->name_lc)
? view($x)
: view('components.attribute');
$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
->with('o',$this)
@ -291,14 +327,19 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
->with('new',$new);
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
return Arr::get($this->values_old,$key);
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),
};
}
public function render_item_new(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
return Arr::get($this->values,$key);
return Arr::get($this->values->dot(),$dotkey);
}
/**
@ -310,20 +351,21 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
{
// If we dont have any objectclasses then we cant know if it is required
return $this->oc->count()
? $this->oc->intersect($this->schema->required_by_object_classes->keys())->sort()
? $this->oc->intersect($this->required_by->keys())->sort()
: collect();
}
/**
* If this attribute has RFC3866 Language Tags, this will enable those values to be captured
*
* @param string $tag
* @param array $value
* @return void
* @deprecated
*/
public function setLangTag(string $tag,array $value): void
public function tagValues(string $tag=Entry::TAG_NOTAG): Collection
{
$this->lang_tags->put($tag,$value);
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,[]));
}
}

View File

@ -5,6 +5,7 @@ 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;
/**
@ -14,13 +15,14 @@ final class JpegPhoto extends Binary
{
use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG): 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

@ -0,0 +1,47 @@
<?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

@ -0,0 +1,17 @@
<?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,13 +20,22 @@ 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,
@ -34,6 +43,8 @@ class Factory
'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,
@ -42,7 +53,9 @@ class Factory
'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,
];

View File

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

View File

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

View File

@ -15,6 +15,8 @@ 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')
@ -24,18 +26,16 @@ final class KrbPrincipalKey extends Attribute
->with('new',$new);
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
$pw = Arr::get($this->values_old,$key);
return $pw
return parent::render_item_old($dotkey)
? str_repeat('*',16)
: NULL;
}
public function render_item_new(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
$pw = Arr::get($this->values,$key);
return $pw
return parent::render_item_new($dotkey)
? str_repeat('*',16)
: NULL;
}

View File

@ -3,9 +3,9 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use Illuminate\Support\Collection;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag
@ -13,6 +13,8 @@ use Illuminate\Support\Collection;
*/
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;

View File

@ -0,0 +1,13 @@
<?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,12 +6,15 @@ 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;
@ -21,25 +24,39 @@ final class ObjectClass extends 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
* @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=[])
{
parent::__construct($dn,$name,$values,['top']);
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$this->values->merge($this->values_old)->unique()->contains($item->name));
$this->set_oc_schema($this->tagValuesOld());
}
public function __get(string $key): mixed
{
return match ($key) {
'structural' => $this->oc_schema->filter(fn($item) => $item->isStructural()),
'structural' => $this->oc_schema->filter(fn($item)=>$item->isStructural()),
default => parent::__get($key),
};
}
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
*
@ -58,7 +75,15 @@ 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,6 +15,9 @@ 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\\';
@ -85,19 +88,23 @@ final class Password extends Attribute
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
$pw = Arr::get($this->values_old,$key);
$pw = parent::render_item_old($dotkey);
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(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
$pw = Arr::get($this->values,$key);
$pw = parent::render_item_new($dotkey);
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

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

View File

@ -14,6 +14,7 @@ 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
{
@ -30,7 +31,7 @@ abstract class Schema extends Attribute
while (! feof($f)) {
$line = trim(fgets($f));
if (! $line OR preg_match('/^#/',$line))
if ((! $line) || preg_match('/^#/',$line))
continue;
$fields = explode(':',$line);
@ -41,12 +42,15 @@ 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);
return Arr::get(($array ? $array->get($string) : []),
$key,
__('No description available, can you help with one?'));
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View

View File

@ -0,0 +1,20 @@
<?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,6 +5,7 @@ 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
@ -41,13 +42,25 @@ class LDIF extends Export
// Display Attributes
foreach ($o->getObjects() as $ao) {
foreach ($ao->values as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
? sprintf('%s: %s',$ao->name,$value)
: sprintf('%s:: %s',$ao->name,base64_encode($value))
,$this->br);
}
if ($ao->no_attr_tags)
foreach ($ao->values as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
? sprintf('%s: %s',$ao->name,$value)
: 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

@ -59,8 +59,6 @@ class LDIF extends Import
$base64encoded = FALSE;
$attribute = NULL;
$value = '';
// Else its a blank line
}
continue;
@ -69,7 +67,7 @@ class LDIF extends Import
$m = [];
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
switch ($x=Arr::get($m,1)) {
switch (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));
@ -133,7 +131,6 @@ 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];
@ -159,8 +156,8 @@ class LDIF extends Import
return $result;
}
public function readEntry() {
static $haveVersion = false;
public function xreadEntry() {
static $haveVersion = FALSE;
if ($lines = $this->nextLines()) {
@ -179,7 +176,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':
@ -201,7 +198,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);
@ -221,13 +218,13 @@ class LDIF extends Import
default:
if (! $server->dnExists($dn))
return $this->error(_('Unkown change type'),$lines);
return $this->error(_('Unknown change type'),$lines);
}
} else
return $this->error(_('A valid dn line is required'),$lines);
} else
return false;
return FALSE;
}
}

View File

@ -6,6 +6,9 @@ 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
*
@ -341,6 +344,11 @@ 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).
*
@ -478,6 +486,28 @@ 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
@ -547,23 +577,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 = collect($array)
->filter()
->map(fn($item)=>config('server')
->schema('objectclasses',$item)
->getSupClasses()
->push($item))
$heirachy = $this->heirachy(collect($array)
->flatten()
->unique();
->filter());
// 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)
&& (! collect($validation->get($this->name_lc))->contains('required'))) {
$validation
->prepend(array_merge(['required','min:1'],$validation->get($this->name_lc.'.0',[])),$this->name_lc.'.0')
->prepend(array_merge(['required','array','min:1'],$validation->get($this->name_lc,[])),$this->name_lc);
->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);
}
return $validation->toArray();

View File

@ -209,7 +209,8 @@ final class Server
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param null $connection
* @param string|null $connection
* @param Carbon|null $cachetime
* @return Entry|null
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
@ -230,7 +231,7 @@ final class Server
/**
* Get the Schema DN
*
* @param $connection
* @param string|null $connection
* @return string
* @throws ObjectNotFoundException
*/
@ -245,16 +246,21 @@ 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): ?LDAPCollection
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(['*','hassubordinates'])
->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
]))
->setDn($dn)
->list()
->orderBy('dn')
->get()) ? $x : NULL;
}
@ -528,7 +534,6 @@ final class Server
*
* @param string $oid
* @return LDAPSyntax|null
* @throws InvalidUsage
*/
public function schemaSyntaxName(string $oid): ?LDAPSyntax
{

View File

@ -97,7 +97,6 @@ class APIController extends Controller
/**
* Return the required and additional attributes for an object class
*
* @param Request $request
* @param string $objectclass
* @return array
*/

View File

@ -57,11 +57,12 @@ class HomeController extends Controller
$o = new Entry;
if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
if (count($x=array_filter(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} = '';
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$o->setRDNBase($key['dn']);
}
@ -92,10 +93,12 @@ class HomeController extends Controller
return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create($dn,$id,[],$request->objectclasses))
->with('o',Factory::create(dn: $dn,attribute: $id,values: [],oc: $request->objectclasses))
->with('value',$request->value)
->with('langtag',Entry::TAG_NOTAG)
->with('loop',$xx)
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),TRUE)->render();
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),new: TRUE,edit: TRUE)
->render();
}
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
@ -188,8 +191,6 @@ class HomeController extends Controller
$result = (new Entry)
->query()
//->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
//->select(['*'])
->setDn($dn)
->recursive()
->get();
@ -237,7 +238,7 @@ class HomeController extends Controller
$password = $o->getObject('userpassword');
$result = collect();
foreach ($password as $key => $value) {
foreach ($password->values->dot() 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]);
@ -269,19 +270,22 @@ class HomeController extends Controller
// We need to process and encrypt the password
if ($request->userpassword) {
$passwords = [];
foreach ($request->userpassword as $key => $value) {
$po = $o->getObject('userpassword');
foreach (Arr::dot($request->userpassword) as $dotkey => $value) {
// If the password is still the MD5 of the old password, then it hasnt changed
if (($old=Arr::get($o->userpassword,$key)) && ($value === md5($old))) {
array_push($passwords,$old);
if (($old=Arr::get($po,$dotkey)) && ($value === md5($old))) {
$passwords[$dotkey] = $value;
continue;
}
if ($value) {
$type = Arr::get($request->userpassword_hash,$key);
array_push($passwords,Password::hash_id($type)->encode($value));
$type = Arr::get($request->userpassword_hash,$dotkey);
$passwords[$dotkey] = Password::hash_id($type)
->encode($value);
}
}
$o->userpassword = $passwords;
$o->userpassword = Arr::undot($passwords);
}
if (! $o->getDirty())
@ -347,7 +351,7 @@ class HomeController extends Controller
return Redirect::to('/')
->withInput()
->with('updated',collect($dirty)->map(fn($key,$item)=>$o->getObject($item)));
->with('updated',collect($dirty)->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
}
/**
@ -372,11 +376,12 @@ class HomeController extends Controller
->with('bases',$this->bases());
// If we are rendering a DN, rebuild our object
$o = config('server')->fetch($key['dn']);
if ($key['dn']) {
$o = config('server')->fetch($key['dn']);
// @todo We need to dynamically exclude request items, so we dont need to add them here
foreach (collect(old())->except(['dn','_token','userpassword_hash']) as $attr => $value)
$o->{$attr} = $value;
foreach (collect(old())->except(['key','dn','step','_token','userpassword_hash']) as $attr => $value)
$o->{$attr} = $value;
}
return match ($key['cmd']) {
'create' => $view
@ -386,7 +391,13 @@ class HomeController extends Controller
'dn' => $view
->with('dn',$key['dn'])
->with('o',$o)
->with('page_actions',collect(['edit'=>TRUE])),
->with('page_actions',collect([
'copy'=>FALSE,
'create'=>FALSE,
'delete'=>TRUE,
'edit'=>TRUE,
'export'=>TRUE,
])),
'import' => $view,

View File

@ -34,10 +34,11 @@ class EntryAddRequest extends FormRequest
if (request()->method() === 'GET')
return [];
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($this->request)
->map(fn($item)=>$item->validation(request()->get('objectclass')))
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->filter()
->flatMap(fn($item)=>$item)
->merge([
@ -60,6 +61,12 @@ 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,10 +13,12 @@ class EntryRequest extends FormRequest
*/
public function rules(): array
{
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($this->request)
->map(fn($item)=>$item->validation(request()?->get('objectclass') ?: []))
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->filter()
->flatMap(fn($item)=>$item)
->toArray();

View File

@ -14,8 +14,17 @@ 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 */
@ -45,13 +54,18 @@ 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
->map(fn($item)=>$item->values)
->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]))
->toArray();
}
@ -64,12 +78,10 @@ class Entry extends Model
{
$key = $this->normalizeAttributeKey($key);
// @todo Silently ignore keys of language tags - we should work with them
if (str_contains($key,';'))
return TRUE;
list($attribute,$tag) = $this->keytag($key);
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key)))
|| (! $this->getObject($key)->isDirty());
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute)))
|| (! $this->getObject($attribute)->isDirty());
}
public static function query(bool $noattrs=false): Builder
@ -86,18 +98,22 @@ 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
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function setAttribute(string $key,mixed $value): static
{
parent::setAttribute($key,$value);
foreach ($value as $k => $v)
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
$key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($key) ?: Factory::create($this->dn ?: '',$key,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($this->attributes[$key]);
$o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($value);
$this->objects->put($key,$o);
@ -158,15 +174,48 @@ class Entry extends Model
if (! is_string($value))
throw new \Exception('value should be a string');
$key = $this->normalizeAttributeKey($key);
$key = $this->normalizeAttributeKey(strtolower($key));
if (! config('server')->schema('attributetypes')->has($key))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
// If the attribute name has tags
list($attribute,$tag) = $this->keytag($key);
$o = $this->objects->get($key) ?: Attribute\Factory::create($this->dn ?: '',$key,[]);
$o->addValue($value);
if (! config('server')->schema('attributetypes')->has($attribute))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
$this->objects->put($key,$o);
$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);
}
}
/**
@ -179,34 +228,26 @@ class Entry extends Model
$result = collect();
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attribute => $values) {
// If the attribute name has tags
$matches = [];
if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
$attribute = $matches[1];
foreach ($this->attributes as $attrtag => $values) {
list($attribute,$tags) = $this->keytag($attrtag);
// If the attribute doesnt exist we'll create it
$o = Arr::get(
$result,
$orig = Arr::get($this->original,$attrtag,[]);
// If the attribute doesnt exist we'll create it
$o = Arr::get(
$result,
$attribute,
Factory::create(
$this->dn,
$attribute,
Factory::create(
$this->dn,
$attribute,
Arr::get($this->original,$attribute,[]),
$entry_oc,
));
$o->setLangTag($matches[3],$values);
[$tags=>$orig],
$entry_oc,
));
} else {
$o = Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),$entry_oc);
}
$o->addValue($tags,$values);
$o->addValueOld($tags,Arr::get($this->original,$attrtag));
if (! $result->has($attribute)) {
// Store our new values to know if this attribute has changed
$o->values = collect($values);
$result->put($attribute,$o);
}
$result->put($attribute,$o);
}
$sort = collect(config('pla.attr_display_order',[]))->map(fn($item)=>strtolower($item));
@ -247,7 +288,7 @@ class Entry extends Model
{
$result = collect();
foreach ($this->objectclass as $oc)
foreach ($this->getObject('objectclass')->values as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result;
@ -274,6 +315,38 @@ 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
*
@ -299,10 +372,36 @@ 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
{
@ -323,12 +422,22 @@ class Entry extends Model
/**
* Return this list of user attributes
*
* @param string $tag If null return all tags
* @return Collection
*/
public function getVisibleAttributes(): Collection
public function getVisibleAttributes(string $tag=''): Collection
{
return $this->objects
->filter(fn($item)=>! $item->is_internal);
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_'];
}
public function hasAttribute(int|string $key): bool
@ -337,36 +446,6 @@ 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
*
@ -374,65 +453,92 @@ class Entry extends Model
*/
public function icon(): string
{
$objectclasses = array_map('strtolower',$this->objectclass);
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value
if (in_array('person',$objectclasses) ||
in_array('organizationalperson',$objectclasses) ||
in_array('inetorgperson',$objectclasses) ||
in_array('account',$objectclasses) ||
in_array('posixaccount',$objectclasses))
if ($objectclasses->intersect([
'account',
'inetorgperson',
'organizationalperson',
'person',
'posixaccount',
])->count())
return 'fas fa-user';
elseif (in_array('organization',$objectclasses))
elseif ($objectclasses->contains('organization'))
return 'fas fa-university';
elseif (in_array('organizationalunit',$objectclasses))
elseif ($objectclasses->contains('organizationalunit'))
return 'fas fa-object-group';
elseif (in_array('posixgroup',$objectclasses) ||
in_array('groupofnames',$objectclasses) ||
in_array('groupofuniquenames',$objectclasses) ||
in_array('group',$objectclasses))
elseif ($objectclasses->intersect([
'posixgroup',
'groupofnames',
'groupofuniquenames',
'group',
])->count())
return 'fas fa-users';
elseif (in_array('dcobject',$objectclasses) ||
in_array('domainrelatedobject',$objectclasses) ||
in_array('domain',$objectclasses) ||
in_array('builtindomain',$objectclasses))
elseif ($objectclasses->intersect([
'dcobject',
'domainrelatedobject',
'domain',
'builtindomain',
])->count())
return 'fas fa-network-wired';
elseif (in_array('alias',$objectclasses))
elseif ($objectclasses->contains('alias'))
return 'fas fa-theater-masks';
elseif (in_array('country',$objectclasses))
return sprintf('flag %s',strtolower(Arr::get($this->c,0)));
elseif ($objectclasses->contains('country'))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0)));
elseif (in_array('device',$objectclasses))
elseif ($objectclasses->contains('device'))
return 'fas fa-mobile-alt';
elseif (in_array('document',$objectclasses))
elseif ($objectclasses->contains('document'))
return 'fas fa-file-alt';
elseif (in_array('iphost',$objectclasses))
elseif ($objectclasses->contains('iphost'))
return 'fas fa-wifi';
elseif (in_array('room',$objectclasses))
elseif ($objectclasses->contains('room'))
return 'fas fa-door-open';
elseif (in_array('server',$objectclasses))
elseif ($objectclasses->contains('server'))
return 'fas fa-server';
elseif (in_array('openldaprootdse',$objectclasses))
elseif ($objectclasses->contains('openldaprootdse'))
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
*

View File

@ -20,7 +20,7 @@ class HasStructuralObjectClass implements ValidationRule
*/
public function validate(string $attribute,mixed $value,Closure $fail): void
{
foreach ($value as $item)
foreach (collect($value)->dot() 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->diff($this->values_old) as $key => $value)
if (md5(Arr::get($this->values_old,$key)) !== $value)
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))
return TRUE;
return FALSE;

View File

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

View File

@ -2,34 +2,39 @@
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;
/**
* Create a new component instance.
*/
public function __construct(LDAPAttribute $o,bool $new=FALSE)
public function __construct(LDAPAttribute $o,bool $new=FALSE,bool $edit=FALSE,string $langtag=Entry::TAG_NOTAG)
{
$this->o = $o;
$this->new = $new;
$this->edit = $edit;
$this->langtag = $langtag;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
public function render(): View
{
return view('components.attribute-type')
->with('o',$this->o)
->with('new',$this->new);
->with('new',$this->new)
->with('edit',$this->edit)
->with('langtag',$this->langtag);
}
}

View File

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

283
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": "2f0a146742112814f55f6a4e5bd12da3",
"content-hash": "ddfbe582d0c27ef08ff1410102ccda28",
"packages": [
{
"name": "brick/math",
@ -288,16 +288,16 @@
},
{
"name": "directorytree/ldaprecord-laravel",
"version": "v3.4.0",
"version": "v3.4.1",
"source": {
"type": "git",
"url": "https://github.com/DirectoryTree/LdapRecord-Laravel.git",
"reference": "bb0aa206723ed07e2b42eadd7311d5949cc770dd"
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/bb0aa206723ed07e2b42eadd7311d5949cc770dd",
"reference": "bb0aa206723ed07e2b42eadd7311d5949cc770dd",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/15f56e01319852d41023633d3688ac4aa139aa6e",
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e",
"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.0"
"source": "https://github.com/DirectoryTree/LdapRecord-Laravel/tree/v3.4.1"
},
"funding": [
{
@ -351,7 +351,7 @@
"type": "github"
}
],
"time": "2025-02-26T01:41:53+00:00"
"time": "2025-03-21T19:16:44+00:00"
},
{
"name": "doctrine/inflector",
@ -588,16 +588,16 @@
},
{
"name": "egulias/email-validator",
"version": "4.0.3",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "b115554301161fa21467629f1e1391c1936de517"
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517",
"reference": "b115554301161fa21467629f1e1391c1936de517",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
"shasum": ""
},
"require": {
@ -643,7 +643,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/4.0.3"
"source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
},
"funding": [
{
@ -651,7 +651,7 @@
"type": "github"
}
],
"time": "2024-12-27T00:36:43+00:00"
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "fruitcake/php-cors",
@ -788,16 +788,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.2",
"version": "7.9.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "d281ed313b989f213357e3be1a179f02196ac99b"
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"shasum": ""
},
"require": {
@ -894,7 +894,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.2"
"source": "https://github.com/guzzle/guzzle/tree/7.9.3"
},
"funding": [
{
@ -910,20 +910,20 @@
"type": "tidelift"
}
],
"time": "2024-07-24T11:22:20+00:00"
"time": "2025-03-27T13:37:11+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.0.4",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"shasum": ""
},
"require": {
@ -977,7 +977,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.4"
"source": "https://github.com/guzzle/promises/tree/2.2.0"
},
"funding": [
{
@ -993,20 +993,20 @@
"type": "tidelift"
}
],
"time": "2024-10-17T10:06:22+00:00"
"time": "2025-03-27T13:27:01+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.0",
"version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"shasum": ""
},
"require": {
@ -1093,7 +1093,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.0"
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
},
"funding": [
{
@ -1109,7 +1109,7 @@
"type": "tidelift"
}
],
"time": "2024-07-18T11:15:46+00:00"
"time": "2025-03-27T12:30:47+00:00"
},
{
"name": "guzzlehttp/uri-template",
@ -1537,16 +1537,16 @@
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.3",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "f379c13663245f7aa4512a7869f62eb14095f23f"
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f379c13663245f7aa4512a7869f62eb14095f23f",
"reference": "f379c13663245f7aa4512a7869f62eb14095f23f",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"shasum": ""
},
"require": {
@ -1594,7 +1594,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2025-02-11T15:03:05+00:00"
"time": "2025-03-19T13:51:03+00:00"
},
{
"name": "laravel/ui",
@ -2212,16 +2212,16 @@
},
{
"name": "monolog/monolog",
"version": "3.8.1",
"version": "3.9.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
"shasum": ""
},
"require": {
@ -2299,7 +2299,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.8.1"
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
},
"funding": [
{
@ -2311,20 +2311,20 @@
"type": "tidelift"
}
],
"time": "2024-12-05T17:15:07+00:00"
"time": "2025-03-24T10:02:05+00:00"
},
{
"name": "nesbot/carbon",
"version": "3.8.6",
"version": "3.9.0",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd"
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6d16a8a015166fe54e22c042e0805c5363aef50d",
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d",
"shasum": ""
},
"require": {
@ -2417,7 +2417,7 @@
"type": "tidelift"
}
],
"time": "2025-02-20T17:33:38+00:00"
"time": "2025-03-27T12:57:33+00:00"
},
{
"name": "nette/schema",
@ -2483,16 +2483,16 @@
},
{
"name": "nette/utils",
"version": "v4.0.5",
"version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
"reference": "ce708655043c7050eb050df361c5e313cf708309"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
"reference": "ce708655043c7050eb050df361c5e313cf708309",
"shasum": ""
},
"require": {
@ -2563,9 +2563,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.5"
"source": "https://github.com/nette/utils/tree/v4.0.6"
},
"time": "2024-08-07T15:39:19+00:00"
"time": "2025-03-30T21:06:30+00:00"
},
{
"name": "nunomaduro/termwind",
@ -3187,16 +3187,16 @@
},
{
"name": "ramsey/collection",
"version": "2.1.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109"
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
"shasum": ""
},
"require": {
@ -3257,9 +3257,9 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.1.0"
"source": "https://github.com/ramsey/collection/tree/2.1.1"
},
"time": "2025-03-02T04:48:29+00:00"
"time": "2025-03-22T05:38:12+00:00"
},
{
"name": "ramsey/uuid",
@ -3429,16 +3429,16 @@
},
{
"name": "symfony/console",
"version": "v7.2.1",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
"shasum": ""
},
"require": {
@ -3502,7 +3502,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.2.1"
"source": "https://github.com/symfony/console/tree/v7.2.5"
},
"funding": [
{
@ -3518,7 +3518,7 @@
"type": "tidelift"
}
],
"time": "2024-12-11T03:49:26+00:00"
"time": "2025-03-12T08:11:12+00:00"
},
{
"name": "symfony/css-selector",
@ -3654,16 +3654,16 @@
},
{
"name": "symfony/error-handler",
"version": "v7.2.4",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5"
"reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/aabf79938aa795350c07ce6464dd1985607d95d5",
"reference": "aabf79938aa795350c07ce6464dd1985607d95d5",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b",
"reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b",
"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.4"
"source": "https://github.com/symfony/error-handler/tree/v7.2.5"
},
"funding": [
{
@ -3725,7 +3725,7 @@
"type": "tidelift"
}
],
"time": "2025-02-02T20:27:07+00:00"
"time": "2025-03-03T07:12:39+00:00"
},
{
"name": "symfony/event-dispatcher",
@ -3949,16 +3949,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.2.3",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0"
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0",
"reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126",
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126",
"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.3"
"source": "https://github.com/symfony/http-foundation/tree/v7.2.5"
},
"funding": [
{
@ -4023,20 +4023,20 @@
"type": "tidelift"
}
],
"time": "2025-01-17T10:56:55+00:00"
"time": "2025-03-25T15:54:33+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.2.4",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "9f1103734c5789798fefb90e91de4586039003ed"
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f1103734c5789798fefb90e91de4586039003ed",
"reference": "9f1103734c5789798fefb90e91de4586039003ed",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
"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.4"
"source": "https://github.com/symfony/http-kernel/tree/v7.2.5"
},
"funding": [
{
@ -4137,7 +4137,7 @@
"type": "tidelift"
}
],
"time": "2025-02-26T11:01:22+00:00"
"time": "2025-03-28T13:32:50+00:00"
},
{
"name": "symfony/mailer",
@ -4941,16 +4941,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.4",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
"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.4"
"source": "https://github.com/symfony/process/tree/v7.2.5"
},
"funding": [
{
@ -4998,7 +4998,7 @@
"type": "tidelift"
}
],
"time": "2025-02-05T08:33:46+00:00"
"time": "2025-03-13T12:21:46+00:00"
},
{
"name": "symfony/routing",
@ -5856,16 +5856,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.15.2",
"version": "v3.15.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729"
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/0bc1e1361e7fffc2be156f46ad1fba6927c01729",
"reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/4ccab20844d18c5af08b68d310e7151a791c3037",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037",
"shasum": ""
},
"require": {
@ -5876,9 +5876,6 @@
"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",
@ -5928,7 +5925,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.2"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.3"
},
"funding": [
{
@ -5940,7 +5937,7 @@
"type": "github"
}
],
"time": "2025-02-25T15:25:22+00:00"
"time": "2025-04-08T15:11:06+00:00"
},
{
"name": "fakerphp/faker",
@ -6007,16 +6004,16 @@
},
{
"name": "filp/whoops",
"version": "2.17.0",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "075bc0c26631110584175de6523ab3f1652eb28e"
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e",
"reference": "075bc0c26631110584175de6523ab3f1652eb28e",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"shasum": ""
},
"require": {
@ -6066,7 +6063,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.17.0"
"source": "https://github.com/filp/whoops/tree/2.18.0"
},
"funding": [
{
@ -6074,7 +6071,7 @@
"type": "github"
}
],
"time": "2025-01-25T12:00:00+00:00"
"time": "2025-03-15T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -6330,38 +6327,39 @@
},
{
"name": "nunomaduro/collision",
"version": "v8.6.1",
"version": "v8.8.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "86f003c132143d5a2ab214e19933946409e0cae7"
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/86f003c132143d5a2ab214e19933946409e0cae7",
"reference": "86f003c132143d5a2ab214e19933946409e0cae7",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"shasum": ""
},
"require": {
"filp/whoops": "^2.16.0",
"filp/whoops": "^2.18.0",
"nunomaduro/termwind": "^2.3.0",
"php": "^8.2.0",
"symfony/console": "^7.2.1"
"symfony/console": "^7.2.5"
},
"conflict": {
"laravel/framework": "<11.39.1 || >=13.0.0",
"phpunit/phpunit": "<11.5.3 || >=12.0.0"
"laravel/framework": "<11.44.2 || >=13.0.0",
"phpunit/phpunit": "<11.5.15 || >=13.0.0"
},
"require-dev": {
"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"
"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"
},
"type": "library",
"extra": {
@ -6424,7 +6422,7 @@
"type": "patreon"
}
],
"time": "2025-01-23T13:41:43+00:00"
"time": "2025-04-03T14:33:09+00:00"
},
{
"name": "phar-io/manifest",
@ -6939,16 +6937,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.12",
"version": "11.5.17",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "d42785840519401ed2113292263795eb4c0f95da"
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d42785840519401ed2113292263795eb4c0f95da",
"reference": "d42785840519401ed2113292263795eb4c0f95da",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"shasum": ""
},
"require": {
@ -6968,14 +6966,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.2",
"sebastian/code-unit": "^3.0.3",
"sebastian/comparator": "^6.3.1",
"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.0",
"sebastian/type": "^5.1.2",
"sebastian/version": "^5.0.2",
"staabm/side-effects-detector": "^1.0.5"
},
@ -7020,7 +7018,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.12"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.17"
},
"funding": [
{
@ -7036,7 +7034,7 @@
"type": "tidelift"
}
],
"time": "2025-03-07T07:31:03+00:00"
"time": "2025-04-08T07:59:11+00:00"
},
{
"name": "sebastian/cli-parser",
@ -7097,16 +7095,16 @@
},
{
"name": "sebastian/code-unit",
"version": "3.0.2",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit.git",
"reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca"
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
"reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64",
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64",
"shasum": ""
},
"require": {
@ -7142,7 +7140,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.2"
"source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3"
},
"funding": [
{
@ -7150,7 +7148,7 @@
"type": "github"
}
],
"time": "2024-12-12T09:59:06+00:00"
"time": "2025-03-19T07:56:08+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@ -7855,16 +7853,16 @@
},
{
"name": "sebastian/type",
"version": "5.1.0",
"version": "5.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"shasum": ""
},
"require": {
@ -7900,7 +7898,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.0"
"source": "https://github.com/sebastianbergmann/type/tree/5.1.2"
},
"funding": [
{
@ -7908,7 +7906,7 @@
"type": "github"
}
],
"time": "2024-09-17T13:12:04+00:00"
"time": "2025-03-18T13:35:50+00:00"
},
{
"name": "sebastian/version",
@ -8455,6 +8453,7 @@
"platform": {
"ext-fileinfo": "*",
"ext-ldap": "*",
"ext-openssl": "*",
"php": "^8.4"
},
"platform-dev": {},

View File

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

View File

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

254
package-lock.json generated
View File

@ -101,13 +101,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
"integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.26.10",
"@babel/types": "^7.26.10",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@ -129,12 +129,12 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
"integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.26.5",
"@babel/compat-data": "^7.26.8",
"@babel/helper-validator-option": "^7.25.9",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
@ -154,9 +154,9 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@ -164,7 +164,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.26.9",
"@babel/traverse": "^7.27.0",
"semver": "^6.3.1"
},
"engines": {
@ -184,9 +184,9 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@ -210,9 +210,9 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
@ -378,25 +378,25 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.10"
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.10"
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -651,12 +651,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
"@babel/helper-plugin-utils": "^7.26.5"
},
"engines": {
"node": ">=6.9.0"
@ -1188,12 +1188,12 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-plugin-utils": "^7.26.5",
"regenerator-transform": "^0.15.2"
},
"engines": {
@ -1325,9 +1325,9 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.26.5"
@ -1509,9 +1509,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -1521,30 +1521,30 @@
}
},
"node_modules/@babel/template": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
"integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.10",
"@babel/parser": "^7.26.10",
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.10",
"@babel/generator": "^7.27.0",
"@babel/parser": "^7.27.0",
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1553,9 +1553,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@ -2021,9 +2021,9 @@
}
},
"node_modules/@types/babel__generator": {
"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==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
@ -2040,9 +2040,9 @@
}
},
"node_modules/@types/babel__traverse": {
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.20.7"
@ -2117,9 +2117,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"license": "MIT"
},
"node_modules/@types/express": {
@ -2248,12 +2248,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
"undici-types": "~6.21.0"
}
},
"node_modules/@types/node-forge": {
@ -2335,9 +2335,9 @@
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
@ -2804,9 +2804,9 @@
}
},
"node_modules/axios": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -2834,13 +2834,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.6",
"@babel/helper-define-polyfill-provider": "^0.6.3",
"@babel/helper-define-polyfill-provider": "^0.6.4",
"semver": "^6.3.1"
},
"peerDependencies": {
@ -2870,12 +2870,12 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.3"
"@babel/helper-define-polyfill-provider": "^0.6.4"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@ -3023,9 +3023,9 @@
"license": "ISC"
},
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz",
"integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==",
"funding": [
{
"type": "github",
@ -3315,9 +3315,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001703",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz",
"integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==",
"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==",
"funding": [
{
"type": "opencollective",
@ -4328,9 +4328,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.115",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.115.tgz",
"integrity": "sha512-MN1nahVHAQMOz6dz6bNZ7apgqc9InZy7Ja4DBEVCTdeiUcegbyOYE9bi/f2Z/z6ZxLi0RxLpyJ3EGe+4h3w73A==",
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -5292,9 +5292,9 @@
}
},
"node_modules/html-entities": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"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==",
"funding": [
{
"type": "github",
@ -5447,9 +5447,9 @@
}
},
"node_modules/http-parser-js": {
"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==",
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
"license": "MIT"
},
"node_modules/http-proxy": {
@ -5467,9 +5467,9 @@
}
},
"node_modules/http-proxy-middleware": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.8",
@ -5634,9 +5634,9 @@
}
},
"node_modules/immutable": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
"license": "MIT"
},
"node_modules/import-fresh": {
@ -5923,9 +5923,9 @@
}
},
"node_modules/jquery.fancytree": {
"version": "2.38.4",
"resolved": "https://registry.npmjs.org/jquery.fancytree/-/jquery.fancytree-2.38.4.tgz",
"integrity": "sha512-f4Fv5jZiZ6pBml/9txcJRAQDZpqQGGoJ8BUbicZKcO4CpgGbqBX9W7eQFwEaKQS0bxdVBLbqWQ9RoUK05ON2kQ==",
"version": "2.38.5",
"resolved": "https://registry.npmjs.org/jquery.fancytree/-/jquery.fancytree-2.38.5.tgz",
"integrity": "sha512-6ntTplhfYKWz74GLpeeE9B62VqhsF+bd80gLZRDD1gl7Vv9WTqqQrCsrGMMu0PB6JLhNOXhf17xIcYpARG+N3g==",
"license": "MIT",
"peerDependencies": {
"jquery": ">=1.9"
@ -6098,9 +6098,9 @@
}
},
"node_modules/less": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz",
"integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz",
"integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
@ -6112,7 +6112,7 @@
"lessc": "bin/lessc"
},
"engines": {
"node": ">=6"
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
@ -6549,9 +6549,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz",
"integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==",
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
@ -8240,9 +8240,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.85.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz",
"integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==",
"version": "1.86.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz",
"integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@ -8832,9 +8832,9 @@
}
},
"node_modules/std-env": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz",
"integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==",
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"license": "MIT"
},
"node_modules/stream-browserify": {
@ -9194,9 +9194,9 @@
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@ -9431,9 +9431,9 @@
}
},
"node_modules/webpack": {
"version": "5.98.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",

View File

@ -1 +1 @@
v2.0.3-rel
v2.1.0-rel

View File

@ -2,7 +2,7 @@
attribute#userPassword .select2-container--bootstrap-5 .select2-selection {
font-size: inherit;
width: 9em;
border: #444054 1px solid;
border: var(--bs-gray-500) 1px solid;
background-color: #f0f0f0;
}
@ -30,7 +30,10 @@ 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,8 +44,10 @@ 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

@ -1,9 +1,9 @@
/*!
=========================================================
* ArchitectUI HTML Theme Dashboard - v4.0.0
* ArchitectUI HTML Theme Dashboard - v4.1.0
=========================================================
* Product Page: https://dashboardpack.com
* Copyright 2023 DashboardPack (https://dashboardpack.com)
* Copyright 2025 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

@ -0,0 +1,22 @@
@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,8 +58,7 @@ $use-all: true;
"~animate-sass/animations/flippers/flipOutY";
// LIGHTSPEED
@import "~animate-sass/animations/lightspeed/lightSpeedIn",
"~animate-sass/animations/lightspeed/lightSpeedOut";
@import "./_animate-override";
// ROTATE
@import "~animate-sass/animations/rotate-enter/rotateIn",
@ -98,4 +97,4 @@ $use-all: true;
"~animate-sass/animations/zoom-exit/zoomOutDown",
"~animate-sass/animations/zoom-exit/zoomOutLeft",
"~animate-sass/animations/zoom-exit/zoomOutRight",
"~animate-sass/animations/zoom-exit/zoomOutUp";
"~animate-sass/animations/zoom-exit/zoomOutUp";

View File

@ -17,19 +17,4 @@
@yield('page_actions')
</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
</div>

View File

@ -0,0 +1,9 @@
<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,7 +15,7 @@
</div>
</div>
<x-attribute :o="$o" :edit="true" :new="$new ?? FALSE"/>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag"/>
</div>
</div>

View File

@ -1,19 +1,21 @@
<!-- $o=Attribute::class -->
<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.'.'.$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))>
<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))
<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())>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
</div>
@else
{{ $value }}
@endif
@endforeach
@else
<input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
@endif
@endforeach
</div>
</x-attribute.layout>

View File

@ -1,17 +1,17 @@
<!-- @todo We are not handling redirect backs yet with updated photos -->
<!-- $o=Binary\JpegPhoto::class -->
<x-attribute.layout :edit="$edit" :new="false" :o="$o">
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
<table class="table table-borderless p-0 m-0">
@foreach ($o->values_old as $value)
@foreach($o->tagValuesOld() as $key => $value)
<tr>
@switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@switch($x=$f->buffer($value,FILEINFO_MIME_TYPE))
@case('image/jpeg')
@default
<td>
<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) }}" />
<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) }}" />
@if ($edit)
@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>

View File

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

View File

@ -1,5 +1,5 @@
<!-- $o=Internal\Timestamp::class -->
@foreach (old($o->name_lc,$o->values) as $value)
@foreach(old($o->name_lc,$o->values) as $value)
@if($loop->index)<br>@endif
{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}
@endforeach

View File

@ -1,8 +1,2 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value)
<div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" @disabled(true)>
</div>
@endforeach
</x-attribute.layout>
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.datetime')

View File

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

View File

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

View File

@ -1,8 +1,2 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value)
<div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ $value }}" @disabled(true)>
</div>
@endforeach
</x-attribute.layout>
<!-- $o=NoAttrTags/Generic::class -->
@include('components.form.disabled.input')

View File

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

View File

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

View File

@ -1,21 +1,21 @@
<!-- $o=KrbTicketFlags::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach(($o->values->count() ? $o->values : ($new ? [0] : NULL)) as $value)
<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" @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 }}" @readonly(true)>
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ $value }}
{{ $o->render_item_old($langtag.'.'.$key) }}
@endif
@endforeach
</x-attribute.layout>

View File

@ -1,12 +1,12 @@
<!-- $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"/>
<!-- $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)
@if($edit)
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/>
@else
{{ $value }}
{{ $o->render_item_old($key) }}
@if ($o->isStructural($value))
<input type="hidden" name="{{ $o->name_lc }}[]" value="{{ $value }}">
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}">
<span class="float-end">@lang('structural')</span>
@endif
<br>

View File

@ -1,11 +1,11 @@
<!-- @todo We are not handling redirect backs yet with updated passwords -->
<!-- $o=Password::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values_old as $value)
<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">
<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)>
<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)>
<div class="invalid-feedback pb-2">
@if($e)
@ -14,7 +14,7 @@
</div>
</div>
@else
{{ (($x=$o->hash($value)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '' }}{{ str_repeat('*',16) }}
{{ $o->render_item_old($langtag.'.'.$key) }}
@endif
@endforeach
</x-attribute.layout>

View File

@ -1,5 +1,5 @@
<!-- $o=RDN::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach(($o->values->count() ? $o->values : ['']) as $value)
@if($edit)
<div class="input-group has-validation mb-3">

View File

@ -0,0 +1 @@
{!! $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.'.'.$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)>
<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)>
@if ($o->isStructural($value))
<span class="input-group-end text-black-50">structural</span>
<span class="input-group-end text-black-50">@lang('structural')</span>
@else
<span class="input-group-end"><i class="fas fa-fw fa-xmark"></i></span>
@endif

View File

@ -1,15 +1,27 @@
@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)
<br/>
<span class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
<button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button>
@elseif($edit && $o->can_addvalues)
@switch(get_class($o))
@case(JpegPhoto::class)
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}" disabled><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span>
@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
@break
@case(ObjectClass::class)
@ -51,6 +63,9 @@
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',
@ -151,7 +166,7 @@
if (x.length) {
x.remove();
// Add this to the must attrs list, because its been rendered
// Add this to the must attrs list, because its been rendered
} else {
attrs.push(mayitem);
}
@ -217,8 +232,24 @@
@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 -->
@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>
@ -229,8 +260,11 @@
// 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')]');
cln.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
cln.find('input:last')
.attr('value','')
.attr('placeholder', '[@lang('NEW')]')
.addClass('border-focus')
.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
});
});
</script>

View File

@ -0,0 +1,8 @@
<!-- $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

@ -0,0 +1,8 @@
<!-- $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($options->count() === 1)
@if(($autoselect ?? FALSE) && $options->count() === 1)
$('#{{ $id ?? $name }}')
.val('{{ $options->first()['id'] }}')
.trigger("change")

View File

@ -0,0 +1,28 @@
@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

@ -0,0 +1,7 @@
<!-- $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->map(fn($item,$key)=>$o->render_item_new($key))->join(',') }}</li>
<li><abbr title="{{ $o->description }}">{{ $o->name }}</abbr>: {{ $o->values->dot()->filter()->join(',') }}</li>
@endforeach
</ul>
</div>

View File

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

View File

@ -2,14 +2,17 @@
<div class="col-12 col-xl-3">
<select id="objectclass" class="form-control">
<option value="-all-">-all-</option>
@foreach ($objectclasses as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@foreach($objectclasses->groupBy(fn($item)=>$item->isStructural()) as $oo)
<optgroup label="{{ __($oo->first()->isStructural() ? 'Structural' : 'Auxillary') }} Object Class"></optgroup>
@foreach($oo as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
@endforeach
</select>
</div>
<div class="col-12 col-xl-9">
@foreach ($objectclasses as $o)
@foreach($objectclasses as $o)
<span id="oc-{{ $o->name_lc }}">
<table class="schema table table-sm table-bordered table-striped">
<thead>
@ -32,10 +35,10 @@
<td>@lang('Inherits from')</td>
<td colspan="3">
<strong>
@if ($o->sup->count() === 0)
@if($o->sup->count() === 0)
@lang('(none)')
@else
@foreach ($o->sup as $sup)
@foreach($o->sup as $sup)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($sup) }}" href="#{{ strtolower($sup) }}">{{ $sup }}</a>
@endforeach
@ -48,12 +51,12 @@
<td>@lang('Parent to')</td>
<td colspan="3">
<strong>
@if (strtolower($o->name) === 'top')
@if(strtolower($o->name) === 'top')
<a class="objectclass" id="-all-">(all)</a>
@elseif (! $o->getChildObjectClasses()->count())
@elseif(! $o->getChildObjectClasses()->count())
@lang('(none)')
@else
@foreach ($o->getChildObjectClasses() as $childoc)
@foreach($o->getChildObjectClasses() as $childoc)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($childoc) }}" href="#{{ strtolower($childoc) }}">{{ $childoc }}</a>
@endforeach
@ -75,7 +78,7 @@
<tr>
<td>
<ul class="ps-3" style="list-style-type: square;">
@foreach ($o->getMustAttrs(TRUE) as $oo)
@foreach($o->getMustAttrs(TRUE) as $oo)
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
@endforeach
</ul>
@ -97,7 +100,7 @@
<tr>
<td>
<ul class="ps-3" style="list-style-type: square;">
@foreach ($o->getMayAttrs(TRUE) as $oo)
@foreach($o->getMayAttrs(TRUE) as $oo)
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
@endforeach
</ul>

View File

@ -1,12 +1,15 @@
@use(App\Ldap\Entry)
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header',['o'=>($oo=$server->fetch(old('container',$container)))])
@include('fragment.dn.header',[
'o'=>($oo=$server->fetch(old('container',$container))),
'langtags'=>collect(),
])
@endsection
@section('main-content')
<x-error/>
<div class="row">
<div class="offset-1 col-10">
<div class="main-card mb-3 card">
@ -28,7 +31,8 @@
<div class="col-12 col-md-6">
<x-form.select
id="objectclass"
name="objectclass[]"
name="objectclass[{{ Entry::TAG_NOTAG }}][]"
old="objectclass.{{ Entry::TAG_NOTAG }}"
:label="__('Select a Structural ObjectClass...')"
:options="($oc=$server->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural())
@ -66,8 +70,8 @@
@endsection
@section('page-scripts')
<script type="text/javascript">
var oc = {!! $oo->getObject('objectclass')->values !!};
var rdn_attr;
function editmode() {
@ -96,31 +100,35 @@
}
$(document).ready(function() {
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
beforeSend: function() {},
success: function(data) {
$('#newattrs').append(data);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false
@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);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
});
// Remove the option from the list
$(this).find('[value="'+item.target.value+'"]').remove()
// If there are no more options
if ($(this).find("option").length === 1)
$('#newattr-select').remove();
});
// Remove the option from the list
$(this).find('[value="'+item.target.value+'"]').remove()
// If there are no more options
if ($(this).find("option").length === 1)
$('#newattr-select').remove();
});
@endif
editmode();
});

View File

@ -1,7 +1,15 @@
@use(App\Ldap\Entry)
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header',['o'=>($o ?? $o=$server->fetch($dn))])
@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')
@ -9,25 +17,30 @@
<div class="col">
<div class="action-buttons float-end">
<ul class="nav">
@if(isset($page_actions) && $page_actions->contains('export'))
@if(isset($page_actions) && $page_actions->get('create'))
<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('New Child')" disabled><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->contains('copy'))
@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->contains('edit')) || old())
@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->contains('delete'))
@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>
@ -38,6 +51,20 @@
</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>
@endsection
@section('main-content')
@ -48,12 +75,9 @@
<div class="main-card mb-3 card">
<div class="card-body">
<div class="card-header-tabs">
<ul class="nav nav-tabs">
<ul class="nav nav-tabs mb-0">
<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">
@ -63,10 +87,57 @@
@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
@foreach ($o->getVisibleAttributes() as $ao)
<x-attribute-type :edit="true" :o="$ao"/>
@endforeach
@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>
@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>
@ -80,26 +151,11 @@
</div>
<!-- Internal Attributes -->
<div class="tab-pane" id="internal" role="tabpanel">
<div class="tab-pane mt-3" 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>
@ -154,6 +210,15 @@
}
$(document).ready(function() {
$('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',

View File

@ -7,12 +7,12 @@
<div class="modal-body">
<table class="table table-bordered p-1">
@foreach(($up=$o->getObject('userpassword'))->values as $key => $value)
@foreach(($up=$o->getObject('userpassword'))->values->dot() as $dotkey => $value)
<tr>
<th>Check</th>
<td>{{ $up->render_item_old($key) }}</td>
<td>{{ $up->render_item_old($dotkey) }}</td>
<td>
<input type="password" style="width: 90%" name="password[{{$key}}]"> <i class="fas fa-fw fa-lock"></i>
<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>

View File

@ -1,7 +1,12 @@
@extends('home')
@section('page_title')
@include('fragment.dn.header')
@include('fragment.dn.header',[
'langtags'=>($langtags=$o->getLangTags()
->flatMap(fn($item)=>$item->values())
->unique()
->sort())
])
@endsection
@section('main-content')
@ -23,6 +28,7 @@
<thead>
<tr>
<th>Attribute</th>
<th>Tag</th>
<th>OLD</th>
<th>NEW</th>
</tr>
@ -31,17 +37,26 @@
<tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr>
<th rowspan="{{ $x=max($oo->values->keys()->max(),$oo->values_old->keys()->max())+1}}">
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th>
@for($xx=0;$xx<$x;$xx++)
@if($xx)
@foreach($oo->values->dot()->keys()->merge($oo->values_old->dot()->keys())->unique() as $dotkey)
@if($loop->index)
</tr><tr>
@endif
<td>{{ (($r=$oo->render_item_old($xx)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ (($r=$oo->render_item_new($xx)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[]" value="{{ Arr::get($oo->values,$xx) }}"></td>
@endfor
<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
</tr>
@endforeach
</tbody>

View File

@ -1,7 +1,6 @@
dn: cn=tech_staff,dc=example.com
cn: tech_staff
labeleduri: ldap:///ou=People,o=Simpsons?uid?one?(&(sn=Simpson)(|(uidNumber
=1000)(uidNumber=1001)))
labeleduri: ldap:///dc=example.com?uid?one?(|(cn=kerberos)(uidNumber=*))
objectclass: nisMailAlias
objectclass: labeledURIObject

View File

@ -0,0 +1,7 @@
dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW:: c2VjcmV0
add: olcRootDN
olcRootDN: cn=config

View File

@ -0,0 +1,25 @@
dn: olcDatabase={3}mdb,cn=config
changetype: modify
add: olcDbMaxSize
olcDbMaxSize: 1073741824
dn: olcDatabase={4}mdb,cn=config
changetype: modify
add: olcDbMaxSize
olcDbMaxSize: 1073741824
dn: olcDatabase={5}mdb,cn=config
changetype: modify
add: olcDbMaxSize
olcDbMaxSize: 1073741824
dn: olcDatabase={6}mdb,cn=config
changetype: modify
add: olcDbMaxSize
olcDbMaxSize: 1073741824
dn: olcDatabase={7}mdb,cn=config
changetype: modify
add: olcDbMaxSize
olcDbMaxSize: 1073741824

View File

@ -0,0 +1,4 @@
dn: olcDatabase={4}mdb,cn=config
changetype: modify
add: olcSizeLimit
olcSizeLimit: 2000

View File

@ -0,0 +1,11 @@
dn: olcOverlay=dynlist,olcDatabase={4}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcDynListConfig
olcOverlay: dynlist
olcDynListAttrSet: nisMailAlias labeledURI
#olcDynListAttrSet: groupOfURLs memberURL memberOf
#olcDynListAttrSet: groupOfURLs memberURL member+dgMemberOf
#olcDynListAttrSet: groupOfURLs memberURL member
#olcDynListAttrSet: groupOfURLs memberURL member
#olcDynListAttrSet: groupOfURLs labeledURI member