Compare commits

..

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

107 changed files with 1157 additions and 2211 deletions

View File

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

View File

@ -10,9 +10,6 @@ assignees: ''
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. (One issue per report please.) 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** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -20,59 +20,39 @@ class Factory
* Map of attributes to appropriate class * Map of attributes to appropriate class
*/ */
public const map = [ public const map = [
'authorityrevocationlist' => CertificateList::class,
'cacertificate' => Certificate::class,
'certificaterevocationlist' => CertificateList::class,
'createtimestamp' => Internal\Timestamp::class, 'createtimestamp' => Internal\Timestamp::class,
'creatorsname' => Internal\DN::class, 'creatorsname' => Internal\DN::class,
'configcontext' => Schema\Generic::class,
'contextcsn' => Internal\CSN::class, 'contextcsn' => Internal\CSN::class,
'entrycsn' => Internal\CSN::class, 'entrycsn' => Internal\CSN::class,
'entrydn' => Internal\DN::class, 'entrydn' => Internal\DN::class,
'entryuuid' => Internal\UUID::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, 'gidnumber' => GidNumber::class,
'hassubordinates' => Internal\HasSubordinates::class, 'hassubordinates' => Internal\HasSubordinates::class,
'jpegphoto' => Binary\JpegPhoto::class, 'jpegphoto' => Binary\JpegPhoto::class,
'modifytimestamp' => Internal\Timestamp::class, 'modifytimestamp' => Internal\Timestamp::class,
'modifiersname' => Internal\DN::class, 'modifiersname' => Internal\DN::class,
'monitorcontext' => Schema\Generic::class,
'namingcontexts' => Schema\Generic::class,
'numsubordinates' => Internal\NumSubordinates::class,
'objectclass' => ObjectClass::class, 'objectclass' => ObjectClass::class,
'pwdpolicysubentry' => Internal\PwdPolicySubentry::class,
'structuralobjectclass' => Internal\StructuralObjectClass::class, 'structuralobjectclass' => Internal\StructuralObjectClass::class,
'subschemasubentry' => Internal\SubschemaSubentry::class, 'subschemasubentry' => Internal\SubschemaSubentry::class,
'supportedcontrol' => Schema\OID::class, 'supportedcontrol' => Schema\OID::class,
'supportedextension' => Schema\OID::class, 'supportedextension' => Schema\OID::class,
'supportedfeatures' => Schema\OID::class, 'supportedfeatures' => Schema\OID::class,
'supportedldapversion' => Schema\Generic::class,
'supportedsaslmechanisms' => Schema\Mechanisms::class, 'supportedsaslmechanisms' => Schema\Mechanisms::class,
'usercertificate' => Certificate::class,
'userpassword' => Password::class, 'userpassword' => Password::class,
]; ];
/** /**
* Create the new Object for an attribute * Create the new Object for an attribute
* *
* @param string $dn
* @param string $attribute * @param string $attribute
* @param array $values * @param array $values
* @param array $oc
* @return Attribute * @return Attribute
*/ */
public static function create(string $dn,string $attribute,array $values,array $oc=[]): Attribute public static function create(string $attribute,array $values): Attribute
{ {
$class = Arr::get(self::map,strtolower($attribute),Attribute::class); $class = Arr::get(self::map,strtolower($attribute),Attribute::class);
Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class)); Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class));
return new $class($dn,$attribute,$values,$oc); return new $class($attribute,$values);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,57 +6,32 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute; use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/** /**
* Represents an ObjectClass Attribute * Represents an ObjectClass Attribute
*/ */
final class ObjectClass extends Attribute final class ObjectClass extends Attribute
{ {
protected(set) bool $no_attr_tags = TRUE;
// The schema ObjectClasses for this objectclass of a DN // The schema ObjectClasses for this objectclass of a DN
protected Collection $oc_schema; protected Collection $oc_schema;
/** public function __construct(string $name,array $values)
* Create an ObjectClass Attribute
*
* @param string $dn DN this attribute is used in
* @param string $name Name of the attribute
* @param array $values Current Values
* @param array $oc 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']); parent::__construct($name,$values);
$this->set_oc_schema($this->tagValuesOld()); $this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$this->values->contains($item->name));
} }
public function __get(string $key): mixed public function __get(string $key): mixed
{ {
return match ($key) { 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), 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 * Is a specific value the structural objectclass
* *
@ -75,15 +50,7 @@ final class ObjectClass extends Attribute
return view('components.attribute.objectclass') return view('components.attribute.objectclass')
->with('o',$this) ->with('o',$this)
->with('edit',$edit) ->with('edit',$edit)
->with('langtag',Entry::TAG_NOTAG)
->with('old',$old) ->with('old',$old)
->with('new',$new); ->with('new',$new);
} }
private function set_oc_schema(Collection $tv): void
{
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$tv->contains($item->name));
}
} }

View File

@ -15,9 +15,6 @@ use App\Traits\MD5Updates;
final class Password extends Attribute final class Password extends Attribute
{ {
use MD5Updates; use MD5Updates;
protected(set) bool $no_attr_tags = TRUE;
private const password_helpers = 'Classes/LDAP/Attribute/Password'; private const password_helpers = 'Classes/LDAP/Attribute/Password';
public const commands = 'App\\Classes\\LDAP\\Attribute\\Password\\'; public const commands = 'App\\Classes\\LDAP\\Attribute\\Password\\';
@ -88,23 +85,19 @@ final class Password extends Attribute
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort()); ->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
} }
public function render_item_old(string $dotkey): ?string public function render_item_old(int $key): ?string
{ {
$pw = parent::render_item_old($dotkey); $pw = Arr::get($this->oldValues,$key);
return $pw return $pw
? (((($x=$this->hash($pw)) && ($x::id() === '*clear*')) ? sprintf('{%s}',$x::shortid()) : '') ? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
.str_repeat('*',16))
: NULL; : NULL;
} }
public function render_item_new(string $dotkey): ?string public function render_item_new(int $key): ?string
{ {
$pw = parent::render_item_new($dotkey); $pw = Arr::get($this->values,$key);
return $pw return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '') ? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
.str_repeat('*',16))
: NULL; : NULL;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/** /**
* Represents an LDAP AttributeType * Represents an LDAP AttributeType
* *
@ -323,12 +320,11 @@ final class AttributeType extends Base {
* that is the list of objectClasses which must have this attribute. * that is the list of objectClasses which must have this attribute.
* *
* @param string $name The name of the objectClass to add. * @param string $name The name of the objectClass to add.
* @param bool $structural
*/ */
public function addRequiredByObjectClass(string $name,bool $structural): void public function addRequiredByObjectClass(string $name): void
{ {
if (! $this->required_by_object_classes->has($name)) if (! $this->required_by_object_classes->contains($name))
$this->required_by_object_classes->put($name,$structural); $this->required_by_object_classes->push($name);
} }
/** /**
@ -336,7 +332,6 @@ final class AttributeType extends Base {
* that is the list of objectClasses which provide this attribute. * that is the list of objectClasses which provide this attribute.
* *
* @param string $name The name of the objectClass to add. * @param string $name The name of the objectClass to add.
* @param bool $structural
*/ */
public function addUsedInObjectClass(string $name,bool $structural): void public function addUsedInObjectClass(string $name,bool $structural): void
{ {
@ -344,11 +339,6 @@ final class AttributeType extends Base {
$this->used_in_object_classes->put($name,$structural); $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). * Gets the names of attributes that are an alias for this attribute (if any).
* *
@ -486,28 +476,6 @@ final class AttributeType extends Base {
return $this->used_in_object_classes; 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 * @return bool
* @deprecated use $this->forced_as_may * @deprecated use $this->forced_as_may
@ -576,22 +544,21 @@ final class AttributeType extends Base {
*/ */
public function validation(array $array): ?array public function validation(array $array): ?array
{ {
// For each item in array, we need to get the OC hierarchy // For each item in array, we need to get the OC heirachy
$heirachy = $this->heirachy(collect($array) $heirachy = collect($array)
->filter()
->map(fn($item)=>config('server')
->schema('objectclasses',$item)
->getSupClasses()
->push($item))
->flatten() ->flatten()
->filter()); ->unique();
// Get any config validation
$validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[])); $validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[]));
if (($heirachy->intersect($this->required_by_object_classes)->count() > 0)
$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'))) { && (! collect($validation->get($this->name_lc))->contains('required'))) {
$validation $validation->put($this->name_lc,array_merge(['required','min:1'],$validation->get($this->name_lc,[])))
->prepend(array_merge(['required','min:1'],$validation->get($nolangtag,[])),$nolangtag) ->put($this->name_lc.'.*',array_merge(['required','min:1'],$validation->get($this->name_lc.'.*',[])));
->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(); return $validation->toArray();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,20 +14,9 @@ use App\Classes\LDAP\Export\LDIF;
use App\Exceptions\Import\AttributeException; use App\Exceptions\Import\AttributeException;
use App\Exceptions\InvalidUsage; use App\Exceptions\InvalidUsage;
/**
* An Entry in an LDAP server
*
* @notes https://ldap.com/ldap-dns-and-rdns
*/
class Entry extends Model 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; private Collection $objects;
/* @deprecated */
private bool $noObjectAttributes = FALSE; private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in // For new entries, this is the container that this entry will be stored in
private string $rdnbase; private string $rdnbase;
@ -54,18 +43,13 @@ class Entry extends Model
/** /**
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes. * This 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 * @return array
* @note $this->attributes may not be updated with changes
*/ */
public function getAttributes(): array public function getAttributes(): array
{ {
return $this->objects return $this->objects
->flatMap(fn($item)=> ->map(fn($item)=>$item->values)
($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(); ->toArray();
} }
@ -78,10 +62,12 @@ class Entry extends Model
{ {
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
list($attribute,$tag) = $this->keytag($key); // @todo Silently ignore keys of language tags - we should work with them
if (str_contains($key,';'))
return TRUE;
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute))) return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key)))
|| (! $this->getObject($attribute)->isDirty()); || (! $this->getObject($key)->isDirty());
} }
public static function query(bool $noattrs=false): Builder public static function query(bool $noattrs=false): Builder
@ -96,26 +82,24 @@ class Entry extends Model
/** /**
* As attribute values are updated, or new ones created, we need to mirror that * As attribute values are updated, or new ones created, we need to mirror that
* into our $objects. This is called when we $o->key = $value * into our $objects
*
* This function should update $this->attributes and correctly reflect changes in $this->objects
* *
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* @return $this * @return $this
*/ */
public function setAttribute(string $key,mixed $value): static public function setAttribute(string $key, mixed $value): static
{ {
foreach ($value as $k => $v) parent::setAttribute($key,$value);
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[])); if ((! $this->objects->get($key)) && $value) {
$o->values = collect($value); $this->objects->put($key,Factory::create($key,$value));
$this->objects->put($key,$o); } elseif ($this->objects->get($key)) {
$this->objects->get($key)->value = $this->attributes[$key];
}
return $this; return $this;
} }
@ -149,106 +133,69 @@ class Entry extends Model
* Return a key to use for sorting * Return a key to use for sorting
* *
* @return string * @return string
* @todo This should be the DN in reverse order
*/ */
public function getSortKeyAttribute(): string public function getSortKeyAttribute(): string
{ {
return collect(explode(',',$this->getDn()))->reverse()->join(','); return $this->getDn();
} }
/* METHODS */ /* METHODS */
/** public function addAttribute(string $key,mixed $value): void
* Add an attribute to this entry, if the attribute already exists, then we'll add the value to the existing item.
*
* This is primarily used by LDIF imports, where attributes have multiple entries over multiple lines
*
* @param string $key
* @param mixed $value
* @return void
* @throws AttributeException
* @note Attributes added this way dont have objectclass information, and the Model::attributes are not populated
*/
public function addAttributeItem(string $key,mixed $value): void
{ {
// While $value is mixed, it can only be a string // While $value is mixed, it can only be a string
if (! is_string($value)) if (! is_string($value))
throw new \Exception('value should be a string'); throw new \Exception('value should be a string');
$key = $this->normalizeAttributeKey(strtolower($key)); $key = $this->normalizeAttributeKey($key);
// If the attribute name has tags if (! config('server')->schema('attributetypes')->has($key))
list($attribute,$tag) = $this->keytag($key); throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
if (! config('server')->schema('attributetypes')->has($attribute)) if ($x=$this->objects->get($key)) {
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute)); $x->addValue($value);
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]); } else {
$o->addValue($tag,[$value]); $this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($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);
} }
} }
/** /**
* Convert all our attribute values into an array of Objects * Convert all our attribute values into an array of Objects
* *
* @param array $attributes
* @return Collection * @return Collection
*/ */
private function getAttributesAsObjects(): Collection public function getAttributesAsObjects(): Collection
{ {
$result = collect(); $result = collect();
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attrtag => $values) { foreach ($this->attributes as $attribute => $value) {
list($attribute,$tags) = $this->keytag($attrtag); // If the attribute name has language tags
$matches = [];
$orig = Arr::get($this->original,$attrtag,[]); if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
$attribute = $matches[1];
// If the attribute doesnt exist we'll create it // If the attribute doesnt exist we'll create it
$o = Arr::get( $o = Arr::get($result,$attribute,Factory::create($attribute,[]));
$result, $o->setLangTag($matches[3],$value);
$attribute,
Factory::create(
$this->dn,
$attribute,
[$tags=>$orig],
$entry_oc,
));
$o->addValue($tags,$values); } else {
$o->addValueOld($tags,Arr::get($this->original,$attrtag)); $o = Factory::create($attribute,$value);
}
if (! $result->has($attribute)) {
// Set the rdn flag
if (preg_match('/^'.$attribute.'=/i',$this->dn))
$o->setRDN();
// Store our original value to know if this attribute has changed
$o->oldValues(Arr::get($this->original,$attribute,[]));
$result->put($attribute,$o); $result->put($attribute,$o);
} }
}
$sort = collect(config('pla.attr_display_order',[]))->map(fn($item)=>strtolower($item)); $sort = collect(config('pla.attr_display_order',[]))->map(fn($item)=>strtolower($item));
@ -288,7 +235,7 @@ class Entry extends Model
{ {
$result = collect(); $result = collect();
foreach ($this->getObject('objectclass')->values as $oc) foreach ($this->objectclass as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes); $result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result; return $result;
@ -315,38 +262,6 @@ class Entry extends Model
->filter(fn($item)=>$item->is_internal); ->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 * Get an attribute as an object
* *
@ -372,36 +287,10 @@ class Entry extends Model
return $this->objects; 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 a list of attributes without any values
* *
* @return Collection * @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 public function getMissingAttributes(): Collection
{ {
@ -411,8 +300,8 @@ class Entry extends Model
private function getRDNObject(): Attribute\RDN private function getRDNObject(): Attribute\RDN
{ {
$o = new Attribute\RDN('','dn',['']); $o = new Attribute\RDN('dn',['']);
// @todo for an existing object, rdnbase would be null, so dynamically get it from the DN. // @todo for an existing object, return the base.
$o->setBase($this->rdnbase); $o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required)); $o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
@ -422,22 +311,12 @@ class Entry extends Model
/** /**
* Return this list of user attributes * Return this list of user attributes
* *
* @param string $tag If null return all tags
* @return Collection * @return Collection
*/ */
public function getVisibleAttributes(string $tag=''): Collection public function getVisibleAttributes(): Collection
{ {
static $cache = []; return $this->objects
->filter(fn($item)=>! $item->is_internal);
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 public function hasAttribute(int|string $key): bool
@ -446,6 +325,36 @@ class Entry extends Model
->has($key); ->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 * Return an icon for a DN based on objectClass
* *
@ -453,97 +362,69 @@ class Entry extends Model
*/ */
public function icon(): string public function icon(): string
{ {
$objectclasses = $this->getObject('objectclass') $objectclasses = array_map('strtolower',$this->objectclass);
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value // Return icon based upon objectClass value
if ($objectclasses->intersect([ if (in_array('person',$objectclasses) ||
'account', in_array('organizationalperson',$objectclasses) ||
'inetorgperson', in_array('inetorgperson',$objectclasses) ||
'organizationalperson', in_array('account',$objectclasses) ||
'person', in_array('posixaccount',$objectclasses))
'posixaccount',
])->count())
return 'fas fa-user'; return 'fas fa-user';
elseif ($objectclasses->contains('organization')) elseif (in_array('organization',$objectclasses))
return 'fas fa-university'; return 'fas fa-university';
elseif ($objectclasses->contains('organizationalunit')) elseif (in_array('organizationalunit',$objectclasses))
return 'fas fa-object-group'; return 'fas fa-object-group';
elseif ($objectclasses->intersect([ elseif (in_array('posixgroup',$objectclasses) ||
'posixgroup', in_array('groupofnames',$objectclasses) ||
'groupofnames', in_array('groupofuniquenames',$objectclasses) ||
'groupofuniquenames', in_array('group',$objectclasses))
'group',
])->count())
return 'fas fa-users'; return 'fas fa-users';
elseif ($objectclasses->intersect([ elseif (in_array('dcobject',$objectclasses) ||
'dcobject', in_array('domainrelatedobject',$objectclasses) ||
'domainrelatedobject', in_array('domain',$objectclasses) ||
'domain', in_array('builtindomain',$objectclasses))
'builtindomain',
])->count())
return 'fas fa-network-wired'; return 'fas fa-network-wired';
elseif ($objectclasses->contains('alias')) elseif (in_array('alias',$objectclasses))
return 'fas fa-theater-masks'; return 'fas fa-theater-masks';
elseif ($objectclasses->contains('country')) elseif (in_array('country',$objectclasses))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0))); return sprintf('flag %s',strtolower(Arr::get($this->c,0)));
elseif ($objectclasses->contains('device')) elseif (in_array('device',$objectclasses))
return 'fas fa-mobile-alt'; return 'fas fa-mobile-alt';
elseif ($objectclasses->contains('document')) elseif (in_array('document',$objectclasses))
return 'fas fa-file-alt'; return 'fas fa-file-alt';
elseif ($objectclasses->contains('iphost')) elseif (in_array('iphost',$objectclasses))
return 'fas fa-wifi'; return 'fas fa-wifi';
elseif ($objectclasses->contains('room')) elseif (in_array('room',$objectclasses))
return 'fas fa-door-open'; return 'fas fa-door-open';
elseif ($objectclasses->contains('server')) elseif (in_array('server',$objectclasses))
return 'fas fa-server'; return 'fas fa-server';
elseif ($objectclasses->contains('openldaprootdse')) elseif (in_array('openldaprootdse',$objectclasses))
return 'fas fa-info'; return 'fas fa-info';
// Default // Default
return 'fa-fw fas fa-cog'; 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 * Dont convert our $this->attributes to $this->objects when creating a new Entry::class
* *
* @return $this * @return $this
* @deprecated
*/ */
public function noObjectAttributes(): static public function noObjectAttributes(): static
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

309
composer.lock generated
View File

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

View File

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

View File

@ -54,7 +54,6 @@
1.3.6.1.4.1.42.2.27.8.5.1:passwordPolicyRequest 1.3.6.1.4.1.42.2.27.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.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.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.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.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 1.3.6.1.4.1.1466.29539.1:LDAP_CONTROL_ATTR_SIZELIMIT

336
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

2
public/js/custom.js vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<!-- $o=Internal::class --> <!-- $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 @if($loop->index)<br>@endif
{{ $value }} {{ $value }}
@endforeach @endforeach

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,12 +26,6 @@
<x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/> <x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/>
</th> </th>
</tr> </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> </table>
</td> </td>
</tr> </tr>

View File

@ -1,4 +1,5 @@
<div class="row"> <div class="row">
<div class="col-12 col-xl-3"> <div class="col-12 col-xl-3">
<select id="attributetype" class="form-control"> <select id="attributetype" class="form-control">
<option value="-all-">-all-</option> <option value="-all-">-all-</option>
@ -105,24 +106,6 @@
@endif @endif
</td> </td>
</tr> </tr>
<tr>
<td>@lang('Required by ObjectClasses')</td>
<td>
@if ($o->required_by_object_classes->count())
@foreach ($o->required_by_object_classes as $class => $structural)
@if($structural)
<strong>
@endif
<a class="objectclass" id="{{ strtolower($class) }}" href="#{{ strtolower($class) }}">{{ $class }}</a>
@if($structural)
</strong>
@endif
@endforeach
@else
@lang('(none)')
@endif
</td>
</tr>
<tr> <tr>
<td>@lang('Force as MAY by config')</td><td><strong>@lang($o->forced_as_may ? 'Yes' : 'No')</strong></td> <td>@lang('Force as MAY by config')</td><td><strong>@lang($o->forced_as_may ? 'Yes' : 'No')</strong></td>
</tr> </tr>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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