Compare commits

..

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

77 changed files with 678 additions and 533 deletions

View File

@ -15,4 +15,4 @@ LDAP_HOST=
LDAP_BASE_DN=
LDAP_USERNAME=
LDAP_PASSWORD=
LDAP_CACHE=false
LDAP_CACHE=true

View File

@ -61,7 +61,7 @@ Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory
- [X] 389 Directory Server
- [ ] 389 Directory Server
If there is an LDAP server that you have that you would like to have supported, please open an issue to request it.
You might need to provide access, provide a copy or instructions to get an environment for testing. If you have enabled

View File

@ -18,7 +18,7 @@ class Attribute implements \Countable, \ArrayAccess
protected string $name;
// Is this attribute an internal attribute
protected ?bool $_is_internal = NULL;
protected(set) bool $is_internal = FALSE;
protected(set) bool $no_attr_tags = FALSE;
// MIN/MAX number of values
@ -144,8 +144,6 @@ class Attribute implements \Countable, \ArrayAccess
'hints' => $this->hints(),
// Can this attribute be edited
'is_editable' => $this->schema ? $this->schema->{$key} : NULL,
// Is this an internal attribute
'is_internal' => is_null($this->_is_internal) ? ($this->used_in->count() === 0) : $this->_is_internal,
// Objectclasses that required this attribute for an LDAP entry
'required' => $this->required(),
// Is this attribute an RDN attribute
@ -159,7 +157,7 @@ class Attribute implements \Countable, \ArrayAccess
// Used in Object Classes
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
// The current attribute values
'values' => ($this->no_attr_tags || $this->is_internal) ? $this->tagValues() : $this->_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,
@ -309,22 +307,15 @@ class Attribute implements \Countable, \ArrayAccess
* @param bool $edit Render an edit form
* @param bool $old Use old value
* @param bool $new Enable adding values
* @param string $langtag Langtag to use when rendering these attribute values
* @param bool $updated Has the entry been updated (uses rendering highlights))
* @return View
*/
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
if ($this->is_internal)
// @note Internal attributes cannot be edited
return view('components.attribute.internal')
->with('o',$this);
$view = match ($this->schema?->syntax_oid) {
$view = match ($this->schema->syntax_oid) {
self::SYNTAX_CERTIFICATE => view('components.syntax.certificate'),
self::SYNTAX_CERTIFICATE_LIST => view('components.syntax.certificatelist'),
default => view()->exists($x='components.attribute.'.$this->name_lc)
default => view()->exists($x = 'components.attribute.' . $this->name_lc)
? view($x)
: view('components.attribute'),
};
@ -333,9 +324,7 @@ class Attribute implements \Countable, \ArrayAccess
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('updated',$updated);
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string

View File

@ -15,7 +15,7 @@ final class JpegPhoto extends Binary
{
use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)
@ -23,7 +23,6 @@ final class JpegPhoto extends Binary
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('updated',$updated)
->with('f',new \finfo);
}
}

View File

@ -24,7 +24,13 @@ class Factory
'cacertificate' => Certificate::class,
'certificaterevocationlist' => CertificateList::class,
'createtimestamp' => Internal\Timestamp::class,
'creatorsname' => Internal\DN::class,
'configcontext' => Schema\Generic::class,
'contextcsn' => Internal\CSN::class,
'entrycsn' => Internal\CSN::class,
'entrydn' => Internal\DN::class,
'entryuuid' => Internal\UUID::class,
'etag' => Internal\Etag::class,
'krblastfailedauth' => Attribute\NoAttrTags\Generic::class,
'krblastpwdchange' => Attribute\NoAttrTags\Generic::class,
'krblastsuccessfulauth' => Attribute\NoAttrTags\Generic::class,
@ -33,11 +39,17 @@ class Factory
'krbprincipalkey' => KrbPrincipalKey::class,
'krbticketflags' => KrbTicketFlags::class,
'gidnumber' => GidNumber::class,
'hassubordinates' => Internal\HasSubordinates::class,
'jpegphoto' => Binary\JpegPhoto::class,
'modifytimestamp' => Internal\Timestamp::class,
'modifiersname' => Internal\DN::class,
'monitorcontext' => Schema\Generic::class,
'namingcontexts' => Schema\Generic::class,
'numsubordinates' => Internal\NumSubordinates::class,
'objectclass' => ObjectClass::class,
'pwdpolicysubentry' => Internal\PwdPolicySubentry::class,
'structuralobjectclass' => Internal\StructuralObjectClass::class,
'subschemasubentry' => Internal\SubschemaSubentry::class,
'supportedcontrol' => Schema\OID::class,
'supportedextension' => Schema\OID::class,
'supportedfeatures' => Schema\OID::class,

View File

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

View File

@ -2,6 +2,8 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute;
/**
@ -9,6 +11,13 @@ use App\Classes\LDAP\Attribute;
*/
abstract class Internal extends Attribute
{
protected ?bool $_is_internal = TRUE;
protected(set) bool $is_internal = TRUE;
protected(set) bool $no_attr_tags = TRUE;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Internal attributes cannot be edited
return view('components.attribute.internal')
->with('o',$this);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,14 +5,13 @@ namespace App\Classes\LDAP\Attribute\Internal;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Internal;
use App\Ldap\Entry;
/**
* Represents an attribute whose values are timestamps
*/
final class Timestamp extends Internal
{
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Internal attributes cannot be edited
return view('components.attribute.internal.timestamp')

View File

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

View File

@ -3,9 +3,9 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@ -17,14 +17,13 @@ final class KrbPrincipalKey extends Attribute
protected(set) bool $no_attr_tags = TRUE;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
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)
->with('updated',$updated);
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string

View File

@ -6,7 +6,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag
@ -50,14 +49,13 @@ final class KrbTicketFlags extends Attribute
return $helpers;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
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('updated',$updated)
->with('helper',static::helpers());
}
}

View File

@ -70,14 +70,14 @@ final class ObjectClass extends Attribute
->contains($value);
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.objectclass')
->with('o',$this)
->with('edit',$edit)
->with('langtag',Entry::TAG_NOTAG)
->with('old',$old)
->with('new',$new)
->with('updated',$updated);
->with('new',$new);
}
private function set_oc_schema(Collection $tv): void

View File

@ -7,7 +7,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@ -79,14 +78,13 @@ final class Password extends Attribute
return ($helpers=static::helpers())->has($id) ? new ($helpers->get($id)) : NULL;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.password')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('updated',$updated)
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
}

View File

@ -6,7 +6,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents the RDN for an Entry
@ -32,7 +31,7 @@ final class RDN extends Attribute
]);
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.rdn')
->with('o',$this);

View File

@ -7,7 +7,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents an attribute whose values are schema related
@ -54,7 +53,7 @@ abstract class Schema extends Attribute
__('No description available, can you help with one?'));
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.internal')

View File

@ -5,14 +5,13 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Ldap\Entry;
/**
* Represents a Generic Schema Attribute
*/
class Generic extends Schema
{
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
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')

View File

@ -5,7 +5,6 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Ldap\Entry;
/**
* Represents a Mechanisms Attribute
@ -34,7 +33,7 @@ final class Mechanisms extends Schema
return parent::_get(config_path('ldap_supported_saslmechanisms.txt'),$string,$key);
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.mechanisms')

View File

@ -5,7 +5,6 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Ldap\Entry;
/**
* Represents an OID Attribute
@ -35,7 +34,7 @@ final class OID extends Schema
return parent::_get(config_path('ldap_supported_oids.txt'),$string,$key);
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.oid')

View File

@ -20,7 +20,7 @@ abstract class Base {
protected string $name = '';
// The OID of this schema item.
protected string $oid = '';
protected string $oid;
# The description of this schema item.
protected string $description = '';

View File

@ -43,7 +43,7 @@ final class ObjectClass extends Base
*
* @param string $line Schema Line
* @param Server $server
* @todo Deprecate this $server variable? It is only used for isForceMay() determination, and that might be better done elsewhere?
* @todo Change $server to $connection, no need to store the server object here
*/
public function __construct(string $line,Server $server)
{

View File

@ -8,11 +8,11 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model;
use LdapRecord\Query\Builder;
use LdapRecord\Query\Collection as LDAPCollection;
use LdapRecord\Query\ObjectNotFoundException;
@ -22,7 +22,8 @@ use App\Ldap\Entry;
final class Server
{
private const LOGKEY = 'SVR';
// Connection information used for these object and children
private ?string $connection;
// This servers schema objectclasses
private Collection $attributetypes;
@ -36,24 +37,22 @@ final class Server
public const OC_ABSTRACT = 0x02;
public const OC_AUXILIARY = 0x03;
public function __construct()
public function __construct(?string $connection=NULL)
{
$this->attributetypes = collect();
$this->ldapsyntaxes = collect();
$this->matchingrules = collect();
$this->objectclasses = collect();
$this->connection = $connection;
}
public function __get(string $key): mixed
{
return match($key) {
'attributetypes' => $this->attributetypes,
'connection' => $this->connection,
'ldapsyntaxes' => $this->ldapsyntaxes,
'matchingrules' => $this->matchingrules,
'objectclasses' => $this->objectclasses,
'config' => config(sprintf('ldap.connections.%s',config('ldap.default'))),
'config' => config('ldap.connections.'.config('ldap.default')),
'name' => Arr::get($this->config,'name',__('No Server Name Yet')),
default => throw new Exception('Unknown key:'.$key),
default => throw new Exception('Unknown key:' . $key),
};
}
@ -63,14 +62,20 @@ final class Server
* Gets the root DN of the specified LDAPServer, or throws an exception if it
* can't find it.
*
* @param string|null $connection Return a collection of baseDNs
* @param bool $objects Return a collection of Entry Models
* @return Collection
* @throws ObjectNotFoundException
* @testedin GetBaseDNTest::testBaseDNExists();
* @todo Need to allow for the scenario if the baseDN is not readable by ACLs
*/
public static function baseDNs(bool $objects=FALSE): Collection
public static function baseDNs(?string $connection=NULL,bool $objects=TRUE): Collection
{
$cachetime = Carbon::now()
->addSeconds(Config::get('ldap.cache.time'));
try {
$rootdse = self::rootDSE();
$base = self::rootDSE($connection,$cachetime);
/**
* LDAP Error Codes:
@ -168,6 +173,16 @@ final class Server
} catch (LdapRecordException $e) {
switch ($e->getDetailedError()?->getErrorCode()) {
case 49:
// Since we failed authentication, we should delete our auth cookie
if (Cookie::has('password_encrypt')) {
Log::alert('Clearing user credentials and logging out');
Cookie::queue(Cookie::forget('password_encrypt'));
Cookie::queue(Cookie::forget('username_encrypt'));
Session::invalidate();
}
abort(401,$e->getDetailedError()->getErrorMessage());
default:
@ -176,99 +191,56 @@ final class Server
}
if (! $objects)
return collect($rootdse->namingcontexts);
return collect($base->namingcontexts);
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($rootdse) {
$result = collect();
/**
* @note While we are caching our baseDNs, it seems if we have more than 1,
* our caching doesnt generate a hit on a subsequent call to this function (before the cache expires).
* IE: If we have 5 baseDNs, it takes 5 calls to this function to case them all.
* @todo Possibly a bug wtih ldaprecord, so need to investigate
*/
$result = collect();
foreach ($base->namingcontexts as $dn)
$result->push((new Entry)->cache($cachetime)->findOrFail($dn));
// @note: Incase our rootDSE didnt return a namingcontext, we'll have no base DNs
foreach (($rootdse->namingcontexts ?: []) as $dn)
$result->push(self::get($dn)->read()->find($dn));
return $result->filter();
});
}
/**
* Work out if we should flush the cache when retrieving an entry
*
* @param string $dn
* @return bool
* @note: We dont need to flush the cache for internal LDAP attributes, as we dont change them
*/
private static function cacheflush(string $dn): bool
{
$cache = (! config('ldap.cache.enabled'))
|| match (strtolower($dn)) {
'','cn=schema','cn=subschema' => FALSE,
default => TRUE,
};
Log::debug(sprintf('%s:%s - %s',self::LOGKEY,$cache ? 'Caching' : 'Not Cached',$dn));
return $cache;
}
/**
* Return our cache time as per the configuration
*
* @return Carbon
*/
private static function cachetime(): Carbon
{
return Carbon::now()
->addSeconds(Config::get('ldap.cache.time'));
}
/**
* Generic Builder method to setup our queries consistently - mainly to ensure we cache results
*
* @param string $dn
* @param array $attrs
* @return Builder
*/
private static function get(string $dn,array $attrs=['*','+']): Builder
{
return Entry::query()
->setDN($dn)
->cache(
until: self::cachetime(),
flush: self::cacheflush($dn))
->select($attrs);
return $result;
}
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @return Model
* @param string|null $connection
* @param Carbon|null $cachetime
* @return Entry|null
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
* @note While we are using a static variable for in session performance, we'll also cache the result normally
*/
public static function rootDSE(): Model
public static function rootDSE(?string $connection=NULL,?Carbon $cachetime=NULL): ?Model
{
static $rootdse = NULL;
$e = new Entry;
if (is_null($rootdse))
$rootdse = self::get('',['+','*'])
->read()
->firstOrFail();
return $rootdse;
return Entry::on($connection ?? $e->getConnectionName())
->cache($cachetime)
->in(NULL)
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
}
/**
* Get the Schema DN
*
* @param string|null $connection
* @return string
* @throws ObjectNotFoundException
*/
public static function schemaDN(): string
public static function schemaDN(?string $connection=NULL): string
{
return collect(self::rootDSE()->subschemasubentry)
->first();
}
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
/* METHODS */
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
}
/**
* Query the server for a DN and return its children and if those children have children.
@ -279,16 +251,17 @@ final class Server
*/
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
{
return $this
->get(
dn: $dn,
attrs: array_merge($attrs,[
'hassubordinates', // Needed for the tree to know if an entry has children
'c' // Needed for the tree to show icons for countries
]))
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(array_merge($attrs,[
'hassubordinates', // Needed for the tree to know if an entry has children
'c' // Needed for the tree to show icons for countries
]))
->setDn($dn)
->list()
->orderBy('dn')
->get() ?: NULL;
->get()) ? $x : NULL;
}
/**
@ -296,13 +269,15 @@ final class Server
*
* @param string $dn
* @param array $attrs
* @return Model|null
* @return Entry|null
*/
public function fetch(string $dn,array $attrs=['*','+']): ?Model
public function fetch(string $dn,array $attrs=['*','+']): ?Entry
{
return $this->get($dn,$attrs)
->read()
->first() ?: NULL;
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select($attrs)
->find($dn)) ? $x : NULL;
}
/**
@ -310,7 +285,6 @@ final class Server
* as configured in config.php.
*
* @return boolean True if the specified attribute is configured to be force as a may attribute
* @todo There are 3 isForceMay() functions - we only need one
*/
public function isForceMay($attr_name): bool
{
@ -347,11 +321,34 @@ final class Server
// First pass if we have already retrieved the schema item
switch ($item) {
case 'attributetypes':
if (isset($this->attributetypes))
return $this->attributetypes;
else
$this->attributetypes = collect();
break;
case 'ldapsyntaxes':
if (isset($this->ldapsyntaxes))
return $this->ldapsyntaxes;
else
$this->ldapsyntaxes = collect();
break;
case 'matchingrules':
if (isset($this->matchingrules))
return $this->matchingrules;
else
$this->matchingrules = collect();
break;
case 'objectclasses':
if ($this->{$item}->count())
return $this->{$item};
if (isset($this->objectclasses))
return $this->objectclasses;
else
$this->objectclasses = collect();
break;
@ -361,9 +358,8 @@ final class Server
}
// Try to get the schema DN from the specified entry.
$schema_dn = $this->schemaDN();
// @note: 389DS does not return subschemaSubentry unless it is requested
$schema = $this->fetch($schema_dn,['*','+','subschemaSubentry']);
$schema_dn = $this->schemaDN($this->connection);
$schema = $this->fetch($schema_dn);
// If our schema's null, we didnt find it.
if (! $schema)
@ -371,7 +367,7 @@ final class Server
switch ($item) {
case 'attributetypes':
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
Log::debug('Attribute Types');
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
@ -389,7 +385,7 @@ final class Server
* with its name set to the alias name, and all other data copied.*/
if ($o->aliases->count()) {
Log::debug(sprintf('%s:\ Attribute [%s] has the following aliases [%s]',self::LOGKEY,$o->name,$o->aliases->join(',')));
Log::debug(sprintf('\ Attribute [%s] has the following aliases [%s]',$o->name,$o->aliases->join(',')));
foreach ($o->aliases as $alias) {
$new_attr = clone $o;
@ -449,7 +445,7 @@ final class Server
return $this->attributetypes;
case 'ldapsyntaxes':
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
Log::debug('LDAP Syntaxes');
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
@ -462,7 +458,7 @@ final class Server
return $this->ldapsyntaxes;
case 'matchingrules':
Log::debug(sprintf('%s:Matching Rules',self::LOGKEY));
Log::debug('Matching Rules');
$this->matchingruleuse = collect();
foreach ($schema->{$item} as $line) {
@ -503,7 +499,7 @@ final class Server
return $this->matchingrules;
case 'objectclasses':
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
Log::debug('Object Classes');
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))

View File

@ -10,18 +10,17 @@ use Illuminate\Support\Collection;
use App\Classes\LDAP\Server;
class AjaxController extends Controller
class APIController extends Controller
{
/**
* Get the LDAP server BASE DNs
*
* @return Collection
* @throws \LdapRecord\Query\ObjectNotFoundException
* @todo This should be consolidated with HomeController
*/
public function bases(): Collection
{
$base = Server::baseDNs(TRUE) ?: collect();
$base = Server::baseDNs() ?: collect();
return $base
->transform(fn($item)=>

View File

@ -5,43 +5,41 @@ namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cookie;
use App\Http\Controllers\Controller;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/';
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')
->except('logout');
}
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
protected function credentials(Request $request): array
{
@ -60,14 +58,17 @@ class LoginController extends Controller
*/
public function logout(Request $request)
{
$user = Auth::user();
// Delete our LDAP authentication cookies
Cookie::queue(Cookie::forget('username_encrypt'));
Cookie::queue(Cookie::forget('password_encrypt'));
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) {
Log::info(sprintf('Logged out [%s]',$user->dn));
return $response;
}
@ -99,4 +100,4 @@ class LoginController extends Controller
{
return login_attr_name();
}
}
}

View File

@ -22,12 +22,13 @@ use App\Exceptions\Import\{GeneralException,VersionException};
use App\Exceptions\InvalidUsage;
use App\Http\Requests\{EntryRequest,EntryAddRequest,ImportRequest};
use App\Ldap\Entry;
use App\View\Components\AttributeType;
class HomeController extends Controller
{
private function bases(): Collection
{
$base = Server::baseDNs(TRUE) ?: collect();
$base = Server::baseDNs() ?: collect();
return $base->transform(function($item) {
return [
@ -44,7 +45,7 @@ class HomeController extends Controller
* Create a new object in the LDAP server
*
* @param EntryAddRequest $request
* @return \Illuminate\View\View
* @return View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
@ -89,20 +90,15 @@ class HomeController extends Controller
$xx->index = 0;
$dn = $request->dn ? Crypt::decrypt($request->dn) : '';
$o = Factory::create(dn: $dn,attribute: $id,values: [],oc: $request->objectclasses);
$view = $request->noheader
return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create(dn: $dn,attribute: $id,values: [],oc: $request->objectclasses))
->with('value',$request->value)
->with('langtag',Entry::TAG_NOTAG)
->with('loop',$xx)
: view('components.attribute-type')
->with('new',TRUE)
->with('edit',TRUE);
return $view
->with('o',$o)
->with('langtag',Entry::TAG_NOTAG)
->with('updated',FALSE);
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),new: TRUE,edit: TRUE)
->render();
}
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
@ -193,7 +189,8 @@ class HomeController extends Controller
{
$dn = Crypt::decryptString($id);
$result = Entry::query()
$result = (new Entry)
->query()
->setDn($dn)
->recursive()
->get();
@ -354,8 +351,7 @@ class HomeController extends Controller
return Redirect::to('/')
->withInput()
->with('updated',collect($dirty)
->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
->with('updated',collect($dirty)->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
}
/**
@ -397,10 +393,10 @@ class HomeController extends Controller
->with('o',$o)
->with('page_actions',collect([
'copy'=>FALSE,
'create'=>($x=($o->getObjects()->except('entryuuid')->count() > 0)),
'delete'=>$x,
'edit'=>$x,
'export'=>$x,
'create'=>TRUE,
'delete'=>TRUE,
'edit'=>TRUE,
'export'=>TRUE,
])),
'import' => $view,

View File

@ -4,7 +4,7 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Cookie;
class AllowAnonymous
{
@ -17,9 +17,7 @@ class AllowAnonymous
*/
public function handle(Request $request,Closure $next): mixed
{
if ((! config('pla.allow_guest',FALSE))
&& ($request->path() !== 'login')
&& ((! Session::has('username_encrypt')) || (! Session::has('password_encrypt'))))
if (((! Cookie::has('username_encrypt')) || (! Cookie::has('password_encrypt'))) && (! config('pla.allow_guest',FALSE)))
return redirect()
->to('/login');

View File

@ -7,6 +7,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use App\Classes\LDAP\Server;
use App\Ldap\User;
/**
* This sets up our application session with any required values, ultimately for cache optimisation reasons
@ -24,6 +25,9 @@ class ApplicationSession
{
Config::set('server',new Server);
view()->share('server', Config::get('server'));
view()->share('user', auth()->user() ?: new User);
return $next($request);
}
}

View File

@ -5,12 +5,11 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\Container;
use App\Ldap\Guard;
use App\Ldap\Connection;
class SwapinAuthUser
{
@ -29,19 +28,25 @@ class SwapinAuthUser
if (! array_key_exists($key,config('ldap.connections')))
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
/*
// Rebuild our connection with the authenticated user.
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Crypt::decryptString(Session::get('username_encrypt')));
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
Log::debug('Swapping out configured LDAP credentials with the user\'s session.',['key'=>$key]);
} else
*/
// @todo it seems sometimes we have cookies that show the logged in user, but Auth::user() has expired?
if (Cookie::has('username_encrypt') && Cookie::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Cookie::get('username_encrypt'));
Config::set('ldap.connections.'.$key.'.password',Cookie::get('password_encrypt'));
Log::debug('Swapping out configured LDAP credentials with the user\'s cookie.',['key'=>$key,'user'=>Cookie::get('username_encrypt')]);
}
// We need to override our Connection object so that we can store and retrieve the logged in user and swap out the credentials to use them.
$c = Container::getInstance()
->getConnection($key);
$c->setConfiguration(config('ldap.connections.'.$key));
$c->setGuardResolver(fn()=>new Guard($c->getLdapConnection(),$c->getConfiguration()));
Container::getInstance()->addConnection(new Connection(config('ldap.connections.'.$key)),$key);
return $next($request);
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use App\Ldap\User;
/**
* This sets up our application session with any required values, ultimately for cache optimisation reasons
*/
class ViewVariables
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request,Closure $next): mixed
{
view()->share('server',Config::get('server'));
view()->share('user',auth()->user() ?: new User);
return $next($request);
}
}

21
app/Ldap/Connection.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Ldap;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Connection as ConnectionBase;
use LdapRecord\LdapInterface;
class Connection extends ConnectionBase
{
public function __construct(DomainConfiguration|array $config=[],?LdapInterface $ldap=NULL)
{
parent::__construct($config,$ldap);
// We need to override this so that we use our own Guard, that stores the users credentials in the session
$this->authGuardResolver = function () {
return new Guard($this->ldap, $this->configuration);
};
}
}

View File

@ -27,6 +27,8 @@ class Entry extends Model
// Our Attribute objects
private Collection $objects;
/* @deprecated */
private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in
private string $rdnbase;
@ -69,6 +71,8 @@ class Entry extends Model
/**
* Determine if the new and old values for a given key are equivalent.
*
* @todo This function barfs on language tags, eg: key = givenname;lang-ja
*/
protected function originalIsEquivalent(string $key): bool
{
@ -80,6 +84,16 @@ class Entry extends Model
|| (! $this->getObject($attribute)->isDirty());
}
public static function query(bool $noattrs=false): Builder
{
$o = new static;
if ($noattrs)
$o->noObjectAttributes();
return $o->newQuery();
}
/**
* 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
@ -274,7 +288,7 @@ class Entry extends Model
{
$result = collect();
foreach (($this->getObject('objectclass')?->values ?: []) as $oc)
foreach ($this->getObject('objectclass')->values as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result;
@ -439,10 +453,9 @@ class Entry extends Model
*/
public function icon(): string
{
$objectclasses = ($x=$this->getObject('objectclass'))
? $x->tagValues()
->map(fn($item)=>strtolower($item))
: collect();
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value
if ($objectclasses->intersect([
@ -526,6 +539,19 @@ class Entry extends Model
return [$attribute,$tags];
}
/**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*
* @return $this
* @deprecated
*/
public function noObjectAttributes(): static
{
$this->noObjectAttributes = TRUE;
return $this;
}
public function setRDNBase(string $bdn): void
{
if ($this->exists)

View File

@ -2,20 +2,26 @@
namespace App\Ldap;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cookie;
// use Illuminate\Support\Facades\Crypt;
use LdapRecord\Auth\Guard as GuardBase;
class Guard extends GuardBase
{
public function attempt(string $username, string $password, bool $stayBound = false): bool
{
Log::info(sprintf('Attempting login for [%s] with password [%s]',$username,($password ? str_repeat('*',16) : str_repeat('?',16))));
if ($result = parent::attempt($username,$password,$stayBound)) {
// Store user details so we can swap in auth details in SwapinAuthUser
session()->put('username_encrypt',Crypt::encryptString($username));
session()->put('password_encrypt',Crypt::encryptString($password));
/*
* We can either use our session or cookies to store this. If using session, then Http/Kernel needs to be
* updated to start a session for API calls.
// We need to store our password so that we can swap in the user in during SwapinAuthUser::class middleware
request()->session()->put('username_encrypt',Crypt::encryptString($username));
request()->session()->put('password_encrypt',Crypt::encryptString($password));
*/
// For our API calls, we store the cookie - which our cookies are already encrypted
Cookie::queue('username_encrypt',$username);
Cookie::queue('password_encrypt',$password);
}
return $result;

View File

@ -31,7 +31,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
return $this->query()->find($credentials['dn']);
// Look for a user using all our baseDNs
foreach (Server::baseDNs(FALSE) as $base) {
foreach (Server::baseDNs() as $base) {
$query = $this->query()->setBaseDn($base);
foreach ($credentials as $key => $value) {
@ -67,7 +67,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
public function findByGuid($guid): ?Model
{
// Look for a user using all our baseDNs
foreach (Server::baseDNs(FALSE) as $base) {
foreach (Server::baseDNs() as $base) {
$user = $this->query()->setBaseDn($base)->findByGuid($guid);
if ($user)

View File

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

View File

@ -2,6 +2,7 @@
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
@ -15,18 +16,19 @@ class Attribute extends Component
public bool $new;
public bool $old;
public string $langtag;
public ?string $na; // Text to render if the LDAPAttribute is null
/**
* Create a new component instance.
*/
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE)
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,?string $na=NULL)
{
$this->o = $o;
$this->edit = $edit;
$this->old = $old;
$this->new = $new;
$this->langtag = $langtag;
$this->updated = $updated;
$this->na = $na;
}
/**
@ -38,7 +40,7 @@ class Attribute extends Component
{
return $this->o
? $this->o
->render(edit: $this->edit,old: $this->old,new: $this->new,langtag: $this->langtag,updated: $this->updated)
: __('Unknown');
->render(edit: $this->edit,old: $this->old,new: $this->new)
: $this->na;
}
}

View File

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

View File

@ -1,27 +1,32 @@
<?php
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser,ViewVariables};
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser};
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup(
group: 'web',
middleware: [
AllowAnonymous::class,
ApplicationSession::class,
SwapinAuthUser::class,
ViewVariables::class,
CheckUpdate::class,
]);
$middleware->appendToGroup('web', [
SwapinAuthUser::class,
ApplicationSession::class,
CheckUpdate::class,
]);
$middleware->prependToGroup('api', [
EncryptCookies::class,
SwapinAuthUser::class,
ApplicationSession::class,
AllowAnonymous::class,
]);
$middleware->trustProxies(at: [
'10.0.0.0/8',

View File

@ -1,5 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\AppServiceProvider::class,
];

104
composer.lock generated
View File

@ -1199,16 +1199,16 @@
},
{
"name": "laravel/framework",
"version": "v11.44.7",
"version": "v11.44.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "00bc6ac91a6d577bf051c18ddaa638c0d221e1c7"
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/00bc6ac91a6d577bf051c18ddaa638c0d221e1c7",
"reference": "00bc6ac91a6d577bf051c18ddaa638c0d221e1c7",
"url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"shasum": ""
},
"require": {
@ -1410,7 +1410,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-04-25T12:40:47+00:00"
"time": "2025-03-12T14:34:30+00:00"
},
{
"name": "laravel/prompts",
@ -1473,16 +1473,16 @@
},
{
"name": "laravel/sanctum",
"version": "v4.1.1",
"version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5"
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"shasum": ""
},
"require": {
@ -1533,7 +1533,7 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2025-04-23T13:03:38+00:00"
"time": "2025-01-26T19:34:36+00:00"
},
{
"name": "laravel/serializable-closure",
@ -1661,16 +1661,16 @@
},
{
"name": "league/commonmark",
"version": "2.6.2",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94"
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94",
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
"shasum": ""
},
"require": {
@ -1764,7 +1764,7 @@
"type": "tidelift"
}
],
"time": "2025-04-18T21:09:27+00:00"
"time": "2024-12-29T14:10:59+00:00"
},
{
"name": "league/config",
@ -5856,16 +5856,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.15.4",
"version": "v3.15.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106"
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106",
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/4ccab20844d18c5af08b68d310e7151a791c3037",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037",
"shasum": ""
},
"require": {
@ -5925,7 +5925,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.3"
},
"funding": [
{
@ -5937,7 +5937,7 @@
"type": "github"
}
],
"time": "2025-04-16T06:32:06+00:00"
"time": "2025-04-08T15:11:06+00:00"
},
{
"name": "fakerphp/faker",
@ -6075,20 +6075,20 @@
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.1.0",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "99ec86beb7da3604d57cd3ca3699d2853f53018d"
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/99ec86beb7da3604d57cd3ca3699d2853f53018d",
"reference": "99ec86beb7da3604d57cd3ca3699d2853f53018d",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
"php": "^5.3|^7.0|^8.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
@ -6096,8 +6096,8 @@
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
"phpunit/php-file-iterator": "^1.4 || ^2.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
},
"type": "library",
"extra": {
@ -6120,9 +6120,9 @@
],
"support": {
"issues": "https://github.com/hamcrest/hamcrest-php/issues",
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.0"
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1"
},
"time": "2025-04-29T18:09:42+00:00"
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "mockery/mockery",
@ -6209,16 +6209,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.1",
"version": "1.13.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
"shasum": ""
},
"require": {
@ -6257,7 +6257,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
},
"funding": [
{
@ -6265,7 +6265,7 @@
"type": "tidelift"
}
],
"time": "2025-04-29T12:36:36+00:00"
"time": "2025-02-12T12:17:51+00:00"
},
{
"name": "nikic/php-parser",
@ -6937,16 +6937,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.18",
"version": "11.5.17",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17"
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc3e887c7f3f9917e1bf61e523413d753db00a17",
"reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"shasum": ""
},
"require": {
@ -7018,7 +7018,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.18"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.17"
},
"funding": [
{
@ -7029,20 +7029,12 @@
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
}
],
"time": "2025-04-22T06:09:49+00:00"
"time": "2025-04-08T07:59:11+00:00"
},
{
"name": "sebastian/cli-parser",
@ -7972,16 +7964,16 @@
},
{
"name": "spatie/backtrace",
"version": "1.7.2",
"version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2"
"reference": "0f2477c520e3729de58e061b8192f161c99f770b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/9807de6b8fecfaa5b3d10650985f0348b02862b2",
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b",
"reference": "0f2477c520e3729de58e061b8192f161c99f770b",
"shasum": ""
},
"require": {
@ -8019,7 +8011,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/backtrace/tree/1.7.2"
"source": "https://github.com/spatie/backtrace/tree/1.7.1"
},
"funding": [
{
@ -8031,7 +8023,7 @@
"type": "other"
}
],
"time": "2025-04-28T14:55:53+00:00"
"time": "2024-12-02T13:28:15+00:00"
},
{
"name": "spatie/error-solutions",

View File

@ -68,7 +68,7 @@ return [
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'info'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],

117
package-lock.json generated
View File

@ -2248,9 +2248,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
"integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==",
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@ -2730,9 +2730,9 @@
}
},
"node_modules/asn1.js/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/assert": {
@ -2804,9 +2804,9 @@
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -2935,9 +2935,9 @@
}
},
"node_modules/bn.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
"license": "MIT"
},
"node_modules/body-parser": {
@ -3315,9 +3315,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001715",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
"integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
"version": "1.0.30001713",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz",
"integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==",
"funding": [
{
"type": "opencollective",
@ -3677,9 +3677,9 @@
}
},
"node_modules/core-js-compat": {
"version": "3.42.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz",
"integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==",
"version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
"integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4"
@ -3722,9 +3722,9 @@
}
},
"node_modules/create-ecdh/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/create-hash": {
@ -4158,9 +4158,9 @@
}
},
"node_modules/diffie-hellman/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/dir-glob": {
@ -4328,9 +4328,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.144",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.144.tgz",
"integrity": "sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==",
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -4349,9 +4349,9 @@
}
},
"node_modules/elliptic/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/emoji-regex": {
@ -4454,9 +4454,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
"license": "MIT"
},
"node_modules/es-object-atoms": {
@ -6410,9 +6410,9 @@
}
},
"node_modules/miller-rabin/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/mime": {
@ -7782,9 +7782,9 @@
}
},
"node_modules/public-encrypt/node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/punycode": {
@ -8240,9 +8240,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.87.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
"version": "1.86.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz",
"integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@ -9099,9 +9099,9 @@
"license": "MIT"
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9431,14 +9431,13 @@
}
},
"node_modules/webpack": {
"version": "5.99.7",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz",
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==",
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
@ -9455,7 +9454,7 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.2",
"schema-utils": "^4.3.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1",
@ -9582,9 +9581,9 @@
"license": "MIT"
},
"node_modules/webpack-dev-middleware/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9694,9 +9693,9 @@
"license": "MIT"
},
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9789,9 +9788,9 @@
"license": "MIT"
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",

View File

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

13
public/css/custom.css vendored
View File

@ -23,10 +23,6 @@ input.form-control.input-group-end {
border-top-right-radius: 4px !important;
}
.custom-tooltip-success {
--bs-tooltip-bg: var(--bs-success);
}
.custom-tooltip-warning {
--bs-tooltip-bg: var(--bs-warning);
--bs-tooltip-color: black;
@ -73,13 +69,4 @@ input.form-control.input-group-end {
/* hide the site icons when the search is opened */
.search-wrapper.active + .header-menu.nav {
display: none;
}
.page-title-wrapper .page-title-items {
margin-left: auto;
max-width: 50%;
}
.page-title-wrapper .page-title-items .page-title-status .alert {
font-size: 0.80em;
}

10
public/css/fixes.css vendored
View File

@ -245,11 +245,6 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
padding: 0.25em 0.45em;
}
/* Remove the shadow outline on an opened box */
.select2-container--bootstrap-5.select2-container--focus .select2-selection, .select2-container--bootstrap-5.select2-container--open .select2-selection {
box-shadow: none;
}
.input-group-text {
background-color: #fafafa;
}
@ -257,9 +252,4 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
/* Stop showing a border on our user's drop down menu when open */
.btn-check:checked+.btn, .btn.active, .btn.show, .btn:first-child:active, :not(.btn-check)+.btn:active {
border-color: var(--bs-btn-bg);
}
/* limit selection to inside the modal */
body.modal-open {
user-select: none;
}

4
public/js/custom.js vendored
View File

@ -59,7 +59,7 @@ $(document).ready(function() {
if (typeof basedn !== 'undefined') {
sources = basedn;
} else {
sources = { url: 'ajax/bases' };
sources = { url: 'api/bases' };
}
// Attach the fancytree widget to an existing <div id="tree"> element
@ -95,7 +95,7 @@ $(document).ready(function() {
source: sources,
lazyLoad: function(event,data) {
data.result = {
url: '/ajax/children',
url: '/api/children',
data: {key: data.node.data.item,depth: 1}
};

View File

@ -1,5 +1,5 @@
<div class="app-page-title">
<div class="page-title-wrapper bg-white">
<div class="page-title-wrapper">
<div class="page-title-heading">
@if (trim($__env->yieldContent('page_icon')))
<div class="page-title-icon f32">
@ -13,14 +13,8 @@
</div>
</div>
<div class="page-title-items p-2">
<div class="page-title-actions">
@yield('page_actions')
</div>
<div class="page-title-status pt-4">
@yield('page_status')
</div>
<div class="page-title-actions">
@yield('page_actions')
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
<!-- $o=AttributeType::class -->
<div class="row pb-3">
<div class="col-12 col-sm-1 col-md-2"></div>
<div class="col-12 col-sm-10 col-md-8">
@ -5,9 +6,6 @@
<div class="col-12 bg-light text-dark p-2">
<strong><abbr title="{{ $o->description }}">{{ $o->name }}</abbr></strong>
<!-- Attribute Hints -->
@if($updated)
<span class="float-end small text-success ms-2" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip-success" title="@lang('Updated')"><i class="fas fa-fw fa-marker"></i> </span>
@endif
<span class="float-end small">
@foreach($o->hints as $name => $description)
@if ($loop->index),@endif
@ -17,7 +15,7 @@
</div>
</div>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag" :updated="$updated"/>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag"/>
</div>
</div>

View File

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

View File

@ -9,7 +9,7 @@
@default
<td>
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}">
<img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'bg-success-subtle'=>$updated]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
<img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
@if($edit)
<br>

View File

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

View File

@ -4,7 +4,7 @@
@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),'bg-success-subtle'=>$updated]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)

View File

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

View File

@ -2,7 +2,7 @@
<x-attribute.layout :edit="$edit" :new="$new" :o="$o" :langtag="$langtag">
@foreach(Arr::get(old($o->name_lc,[$langtag=>$new ? [NULL] : $o->tagValues($langtag)]),$langtag,[]) as $key => $value)
@if($edit)
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :langtag="$langtag" :updated="$updated" :value="$value" :loop="$loop" />
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value" :langtag="$langtag"/>
@else
{{ $o->render_item_old($key) }}
@if ($o->isStructural($value))

View File

@ -5,7 +5,7 @@
@if($edit)
<div class="input-group has-validation mb-3">
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[{{ $langtag }}][]" :value="$o->hash($value)->id()" :options="$helpers" allowclear="false" :disabled="true"/>
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value),'bg-success-subtle'=>$updated]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)

View File

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

View File

@ -2,7 +2,6 @@
@use(App\Classes\LDAP\Attribute\CertificateList)
@use(App\Classes\LDAP\Attribute\Binary\JpegPhoto)
@use(App\Classes\LDAP\Attribute\ObjectClass)
@php($clone=FALSE)
<span class="p-0 m-0">
@if($o->is_rdn)
@ -55,9 +54,6 @@
var rendered = false;
var newadded = [];
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
if (newadded.length)
process_oc();
@ -92,7 +88,7 @@
// Get a list of attributes already on the page, so we dont double up
$.ajax({
method: 'POST',
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
// Render any must attributes
@ -157,7 +153,7 @@
$.ajax({
method: 'POST',
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
var attrs = [];

View File

@ -1,5 +1,11 @@
@if($errors->any())
<div class="alert alert-danger p-2">
<p class="m-0"><i class="fas fa-fw fa-thumbs-down"></i> @lang('Validation Errors')</p>
<div class="alert alert-danger">
<h4 class="alert-heading"><i class="fas fa-fw fa-thumbs-down"></i> Error?</h4>
<hr>
<ul style="list-style-type: square;">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

View File

@ -1,5 +1,7 @@
@if(session()->has('note'))
<div class="alert alert-info p-2">
<p class="m-0"><i class="fas fa-fw fa-info"></i> {{ session()->pull('note') }}</p>
<div class="alert alert-info">
<h4 class="alert-heading"><i class="fas fa-fw fa-note-sticky"></i> Note:</h4>
<hr>
<p>{{ session()->pull('note') }}</p>
</div>
@endif

View File

@ -1,5 +1,12 @@
@if(session()->has('updated'))
<div class="alert alert-success p-2">
<p class="m-0"><i class="fas fa-fw fa-pen-to-square"></i> @lang('Entry updated') [{{ session()->get('updated')->count() }} @lang('attributes(s)')]</p>
<div class="alert alert-success">
<h4 class="alert-heading"><i class="fas fa-fw fa-thumbs-up"></i> Success!</h4>
<hr>
<p>{{ __('Entry updated') }}</p>
<ul style="list-style-type: square;">
@foreach (session()->pull('updated') as $key => $o)
<li><abbr title="{{ $o->description }}">{{ $o->name }}</abbr>: {{ $o->values->dot()->filter()->join(',') }}</li>
@endforeach
</ul>
</div>
@endif

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach($server->baseDNs(TRUE)->sort(fn($item)=>$item->sort_key) as $item)
@foreach($server->baseDNs()->sort(fn($item)=>$item->sort_key) as $item)
<tr>
<td class="ps-0">{{ $item->getDn() }}</td>
</tr>

View File

@ -3,7 +3,7 @@
<td class="p-1 pt-0" rowspan="2">
{!! ($x=$o->getObject('jpegphoto')) ? $x->render(FALSE,TRUE) : sprintf('<div class="page-title-icon f32 m-2"><i class="%s"></i></div>',$o->icon() ?? "fas fa-info") !!}
</td>
<td class="text-end align-bottom pb-0 mb-0 pt-2 pe-3 {{ $x ? 'ps-3' : '' }}"><strong class="user-select-all">{{ $o->getDn() }}</strong></td>
<td class="text-end align-bottom pb-0 mb-0 pt-2 pe-3 {{ $x ? 'ps-3' : '' }}"><strong>{{ $o->getDn() }}</strong></td>
</tr>
<tr>
<td class="align-bottom" style="font-size: 55%" colspan="2">
@ -11,19 +11,19 @@
<tr class="mt-1">
<td class="p-0 pe-2">Created</td>
<th class="p-0">
<x-attribute :o="$o->getObject('createtimestamp')"/> [<x-attribute :o="$o->getObject('creatorsname')"/>]
<x-attribute :o="$o->getObject('createtimestamp')" :na="__('Unknown')"/> [<x-attribute :o="$o->getObject('creatorsname')" :na="__('Unknown')"/>]
</th>
</tr>
<tr class="mt-1">
<td class="p-0 pe-2">Modified</td>
<th class="p-0">
<x-attribute :o="$o->getObject('modifytimestamp')"/> [<x-attribute :o="$o->getObject('modifiersname')"/>]
<x-attribute :o="$o->getObject('modifytimestamp')" :na="__('Unknown')"/> [<x-attribute :o="$o->getObject('modifiersname')" :na="__('Unknown')"/>]
</th>
</tr>
<tr class="mt-1">
<td class="p-0 pe-2">UUID</td>
<th class="p-0">
<x-attribute :o="$o->getObject('entryuuid')"/>
<x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/>
</th>
</tr>
@if($langtags->count())

View File

@ -9,11 +9,9 @@
])
@endsection
@section('page_status')
<x-error/>
@endsection
@section('main-content')
<x-error/>
<div class="row">
<div class="offset-1 col-10">
<div class="main-card mb-3 card">
@ -49,10 +47,10 @@
@break
@case(2)
<x-attribute-type :o="$o->getObject('rdn')" :edit="TRUE" :new="FALSE" :langtag="Entry::TAG_NOTAG" :updated="FALSE"/>
<x-attribute-type :edit="true" :o="$o->getObject('rdn')"/>
@foreach ($o->getVisibleAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :langtag="Entry::TAG_NOTAG" :updated="FALSE"/>
<x-attribute-type :edit="true" :o="$ao"/>
@endforeach
@include('fragment.dn.add_attr')
@ -105,10 +103,9 @@
$(document).ready(function() {
@if($step === 2)
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
var oc = {!! $o->getObject('objectclass')->values !!};
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
url: '{{ url('entry/attr/add') }}/'+item.target.value,

View File

@ -51,25 +51,27 @@
</div>
</div>
</div>
@endsection
@section('page_status')
@if(($x=$o->getOtherTags()->filter(fn($item)=>$item->diff(['binary'])->count()))->count())
<div class="alert alert-danger p-2">
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 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>
@elseif(($x=$o->getLangMultiTags())->count())
<div class="alert alert-danger p-2">
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
<x-note/>
<x-error/>
<x-updated/>
</div>
@endsection
@section('main-content')
<x-note/>
<x-updated/>
<x-error/>
<div class="main-card mb-3 card">
<div class="card-body">
<div class="card-header-tabs">
@ -88,14 +90,8 @@
<div class="card-header border-bottom-0">
<div class="btn-actions-pane-right">
<div role="group" class="btn-group-sm nav btn-group">
@php
$langtags->prepend(Entry::TAG_NOTAG);
if (isset($page_actions) && $page_actions->get('edit'))
$langtags->push('+');
@endphp
@foreach($langtags as $tag)
<a data-bs-toggle="tab" href="#tab-lang-{{ $tag ?: '_default' }}" @class(['btn','btn-outline-light','border-dark-subtle','active'=>!$loop->index])>
@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>
@ -117,13 +113,12 @@
<div class="card-body">
<div class="tab-content">
@php($up=(session()->pull('updated') ?: collect()))
@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 :o="$ao" :edit="TRUE" :new="FALSE" :langtag="$tag" :updated="$up->contains($ao->name_lc)"/>
<x-attribute-type :edit="true" :o="$ao" :langtag="$tag"/>
@endforeach
@break
@ -136,7 +131,7 @@
@default
@foreach ($o->getVisibleAttributes($langtag=sprintf('lang-%s',$tag)) as $ao)
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :langtag="$langtag" :updated="$up->contains($ao->name_lc)"/>
<x-attribute-type :edit="true" :o="$ao" :langtag="$langtag"/>
@endforeach
@endswitch
</div>
@ -158,7 +153,7 @@
<!-- Internal Attributes -->
<div class="tab-pane mt-3" id="internal" role="tabpanel">
@foreach ($o->getInternalAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="FALSE" :new="FALSE" :langtag="Entry::TAG_NOTAG" :updated="FALSE"/>
<x-attribute-type :o="$ao"/>
@endforeach
</div>
</div>
@ -179,6 +174,7 @@
@section('page-scripts')
<script type="text/javascript">
var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function editmode() {
$('#dn-edit input[name="dn"]').val(dn);
@ -229,9 +225,6 @@
});
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({
type: 'POST',
beforeSend: function() {},
@ -341,11 +334,6 @@
}
});
$('#page-modal').on('hide.bs.modal',function() {
// Clear any select ranges that occurred while the modal was open
document.getSelection().removeAllRanges();
});
@if(old())
editmode();
@endif

View File

@ -1,10 +1,11 @@
@use(App\Classes\LDAP\Server)
@extends('layouts.dn')
@section('page_title')
<table class="table table-borderless">
<tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td>
<td class="top text-end align-text-top p-2"><strong>{{ $server->schemaDN() }}</strong></td>
<td class="top text-end align-text-top p-2"><strong>{{ Server::schemaDN() }}</strong></td>
</tr>
</table>
@endsection
@ -57,7 +58,7 @@
return false;
$.ajax({
url: '{{ url('ajax/schema/view') }}',
url: '{{ url('api/schema/view') }}',
method: 'POST',
data: { type: type },
dataType: 'html',

View File

@ -5,7 +5,7 @@
</div>
<div class="modal-body">
<div id="entry_export" style="user-select: text;"></div>
<div id="entry_export"></div>
</div>
<div class="modal-footer">

View File

@ -9,11 +9,11 @@
])
@endsection
@section('page_status')
<x-error/>
@endsection
@section('main-content')
<x-note/>
<x-success/>
<x-error/>
<div class="main-card mb-3 card">
<form id="dn-update" method="POST" class="needs-validation" action="{{ url('entry/update/commit') }}" novalidate>
@csrf
@ -37,7 +37,7 @@
<tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr>
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())}}">
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th>
@ -54,7 +54,7 @@
<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 }}[{{ $oo->no_attr_tags ? \App\Ldap\Entry::TAG_NOTAG : collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo->values->dot(),$dotkey) }}"></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>

23
routes/api.php Normal file
View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\APIController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::controller(APIController::class)->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});

View File

@ -2,7 +2,7 @@
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\{AjaxController,HomeController};
use App\Http\Controllers\HomeController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Middleware\AllowAnonymous;
@ -57,13 +57,4 @@ Route::controller(HomeController::class)->group(function() {
Route::view('modal/export/{dn}','modals.entry-export');
Route::view('modal/userpassword-check/{dn}','modals.entry-userpassword-check');
});
});
Route::controller(AjaxController::class)
->prefix('ajax')
->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});
});

View File

@ -12,7 +12,7 @@ class ExampleTest extends TestCase
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/login');
$response = $this->get('/');
$response->assertStatus(200);
}

View File

@ -13,10 +13,11 @@ class GetBaseDNTest extends TestCase
*
* @return void
* @throws \LdapRecord\Query\ObjectNotFoundException
* @covers \App\Classes\LDAP\Server::baseDNs()
*/
public function testBaseDnExists()
{
$o = Server::baseDNs(TRUE);
$o = Server::baseDNs();
$this->assertIsObject($o);
$this->assertCount(6,$o->toArray());