Compare commits

...

20 Commits

Author SHA1 Message Date
471ccfd88e Release v2.1.2
Some checks failed
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
2025-05-01 12:12:20 +09:30
5d23cbf0cc If a user doesnt have permission to see an entries attributes - but can see the entry, disable edit and dont attempt to render. Further, if they cant see the objectclasses, dont make additional attributes available 2025-05-01 12:12:20 +09:30
b9ae269895 Fix html syntax error that stopped rendering uuid for a DN 2025-05-01 11:02:58 +09:30
ae782577e7 Automatically work out if attributes are internal (because they are not used in objectclasses)
Some checks failed
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
2025-04-30 21:46:57 +09:30
84f82aaf59 Cosmetic layout change for DNs (extending the background to the page width). 2025-04-30 21:46:57 +09:30
10eca55026 NPM and framework update 2025-04-30 09:13:58 +09:30
64cc21d819 Fix logic processing isDirty() for MD5Update apps by checking that the new & old values dont equal before checking their md5 value 2025-04-30 09:04:10 +09:30
3d511f3fae Change rendering of notes/errors/update and highlight attributes that are successfully updated 2025-04-30 09:04:10 +09:30
bab5a2626d Remove Components/AttributeType::class it wasnt providing any functionality 2025-04-30 09:04:10 +09:30
6954b09089 @todo udpates 2025-04-27 14:12:24 +10:00
a336e58b7a Fixes for 389 Directory Server - addresses recursion issue #314. The primary issue was that 389DS doesnt render the subschemaSubentry attribute unless it is specifically requested. 2025-04-27 14:12:24 +10:00
53880121b6 Server::class optimisations, minimal functional changes - basically caching/performance improvements 2025-04-27 14:12:24 +10:00
ea46cf36d0 Remove deprecteated Entry::query() override and associated noObjectAttributes() it wasnt used 2025-04-27 14:12:24 +10:00
36f8f57b77 When opening the export modal, limit selection to inside the modal. Generally when opening modals disable selection.
When selecting a DN on a DN fragment, autoselect the whole DN.
2025-04-27 14:12:24 +10:00
3604f1498c Update existing LDAP instance configuration instead of replacing it. Caching was not enabled as per the configuration, so this fixes this. 2025-04-27 14:12:24 +10:00
808934ebfe Change we now store logged in user details in session, instead of cookies.
This is so when the session expires, the logged in user details are expired as well, which wasnt happening with cookies.
2025-04-27 14:12:24 +10:00
21a690c6dd Move our /api routes into /ajax under web.php. The /api routes werent authenticated and may not have been using the logged in users details 2025-04-27 14:12:24 +10:00
0083e9158b Move out view variables until after our session has been setup. This was needed so that auth()->user() could be resolved correctly and needed to be done after we have started the session and swapped in the users cookies 2025-04-27 14:12:24 +10:00
f4cc559931 Dynamically work out objectclasses on the current entry, this fixes usage issues between adding objectclasses and adding attribute that are now available from new objectclasses, as well as determining that they are not dynamic 2025-04-27 14:12:24 +10:00
3de46ac28e Fix when rendering changes to 2 or more attributes, the update confirmation table had one too many rowspan values for the Attribute.
Fix updating an entry by adding an new objectclass
2025-04-27 14:12:24 +10:00
77 changed files with 524 additions and 674 deletions

View File

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

View File

@ -61,7 +61,7 @@ Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory
- [ ] 389 Directory Server
- [X] 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(set) bool $is_internal = FALSE;
protected ?bool $_is_internal = NULL;
protected(set) bool $no_attr_tags = FALSE;
// MIN/MAX number of values
@ -144,6 +144,8 @@ 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
@ -157,7 +159,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->tagValues() : $this->_values,
'values' => ($this->no_attr_tags || $this->is_internal) ? $this->tagValues() : $this->_values,
// The original attribute values
'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old,
@ -307,15 +309,22 @@ 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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
$view = match ($this->schema->syntax_oid) {
if ($this->is_internal)
// @note Internal attributes cannot be edited
return view('components.attribute.internal')
->with('o',$this);
$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'),
};
@ -324,7 +333,9 @@ class Attribute implements \Countable, \ArrayAccess
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
->with('new',$new)
->with('langtag',$langtag)
->with('updated',$updated);
}
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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)
@ -23,6 +23,7 @@ final class JpegPhoto extends Binary
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('updated',$updated)
->with('f',new \finfo);
}
}

View File

@ -24,13 +24,7 @@ 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,
@ -39,17 +33,11 @@ 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,5 +9,4 @@ use App\Classes\LDAP\Attribute;
*/
final class GidNumber extends Attribute
{
protected(set) bool $no_attr_tags = FALSE;
}

View File

@ -2,8 +2,6 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute;
/**
@ -11,13 +9,6 @@ use App\Classes\LDAP\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
{
// @note Internal attributes cannot be edited
return view('components.attribute.internal')
->with('o',$this);
}
}

View File

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

View File

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

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 HasSubordinates Attribute
*/
final class HasSubordinates 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,12 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an StructuralObjectClass Attribute
*/
final class StructuralObjectClass extends Internal
{
}

View File

@ -1,12 +0,0 @@
<?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,13 +5,14 @@ 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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
// @note Internal attributes cannot be edited
return view('components.attribute.internal.timestamp')

View File

@ -1,12 +0,0 @@
<?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,13 +17,14 @@ final class KrbPrincipalKey extends Attribute
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,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
return view('components.attribute.krbprincipalkey')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
->with('new',$new)
->with('updated',$updated);
}
public function render_item_old(string $dotkey): ?string

View File

@ -6,6 +6,7 @@ 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
@ -49,13 +50,14 @@ final class KrbTicketFlags extends Attribute
return $helpers;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=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('new',$new)
->with('updated',$updated);
}
private function set_oc_schema(Collection $tv): void

View File

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

View File

@ -7,6 +7,7 @@ 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
@ -53,7 +54,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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.internal')

View File

@ -5,13 +5,14 @@ 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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.generic')

View File

@ -5,6 +5,7 @@ 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
@ -33,7 +34,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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.mechanisms')

View File

@ -5,6 +5,7 @@ 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
@ -34,7 +35,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): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=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 Change $server to $connection, no need to store the server object here
* @todo Deprecate this $server variable? It is only used for isForceMay() determination, and that might be better done elsewhere?
*/
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,8 +22,7 @@ use App\Ldap\Entry;
final class Server
{
// Connection information used for these object and children
private ?string $connection;
private const LOGKEY = 'SVR';
// This servers schema objectclasses
private Collection $attributetypes;
@ -37,22 +36,24 @@ final class Server
public const OC_ABSTRACT = 0x02;
public const OC_AUXILIARY = 0x03;
public function __construct(?string $connection=NULL)
public function __construct()
{
$this->connection = $connection;
$this->attributetypes = collect();
$this->ldapsyntaxes = collect();
$this->matchingrules = collect();
$this->objectclasses = collect();
}
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('ldap.connections.'.config('ldap.default')),
'config' => config(sprintf('ldap.connections.%s',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),
};
}
@ -62,20 +63,14 @@ 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(?string $connection=NULL,bool $objects=TRUE): Collection
public static function baseDNs(bool $objects=FALSE): Collection
{
$cachetime = Carbon::now()
->addSeconds(Config::get('ldap.cache.time'));
try {
$base = self::rootDSE($connection,$cachetime);
$rootdse = self::rootDSE();
/**
* LDAP Error Codes:
@ -173,16 +168,6 @@ 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:
@ -191,57 +176,100 @@ final class Server
}
if (! $objects)
return collect($base->namingcontexts);
return collect($rootdse->namingcontexts);
/**
* @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));
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($rootdse) {
$result = collect();
return $result;
// @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);
}
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param string|null $connection
* @param Carbon|null $cachetime
* @return Entry|null
* @return Model
* @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(?string $connection=NULL,?Carbon $cachetime=NULL): ?Model
public static function rootDSE(): Model
{
$e = new Entry;
static $rootdse = NULL;
return Entry::on($connection ?? $e->getConnectionName())
->cache($cachetime)
->in(NULL)
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
if (is_null($rootdse))
$rootdse = self::get('',['+','*'])
->read()
->firstOrFail();
return $rootdse;
}
/**
* Get the Schema DN
*
* @param string|null $connection
* @return string
* @throws ObjectNotFoundException
*/
public static function schemaDN(?string $connection=NULL): string
public static function schemaDN(): string
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
return collect(self::rootDSE()->subschemasubentry)
->first();
}
/* METHODS */
/**
* Query the server for a DN and return its children and if those children have children.
*
@ -251,17 +279,16 @@ final class Server
*/
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(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)
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
]))
->list()
->orderBy('dn')
->get()) ? $x : NULL;
->get() ?: NULL;
}
/**
@ -269,15 +296,13 @@ final class Server
*
* @param string $dn
* @param array $attrs
* @return Entry|null
* @return Model|null
*/
public function fetch(string $dn,array $attrs=['*','+']): ?Entry
public function fetch(string $dn,array $attrs=['*','+']): ?Model
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select($attrs)
->find($dn)) ? $x : NULL;
return $this->get($dn,$attrs)
->read()
->first() ?: NULL;
}
/**
@ -285,6 +310,7 @@ 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
{
@ -321,34 +347,11 @@ 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 (isset($this->objectclasses))
return $this->objectclasses;
else
$this->objectclasses = collect();
if ($this->{$item}->count())
return $this->{$item};
break;
@ -358,8 +361,9 @@ final class Server
}
// Try to get the schema DN from the specified entry.
$schema_dn = $this->schemaDN($this->connection);
$schema = $this->fetch($schema_dn);
$schema_dn = $this->schemaDN();
// @note: 389DS does not return subschemaSubentry unless it is requested
$schema = $this->fetch($schema_dn,['*','+','subschemaSubentry']);
// If our schema's null, we didnt find it.
if (! $schema)
@ -367,7 +371,7 @@ final class Server
switch ($item) {
case 'attributetypes':
Log::debug('Attribute Types');
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
@ -385,7 +389,7 @@ final class Server
* with its name set to the alias name, and all other data copied.*/
if ($o->aliases->count()) {
Log::debug(sprintf('\ Attribute [%s] has the following aliases [%s]',$o->name,$o->aliases->join(',')));
Log::debug(sprintf('%s:\ Attribute [%s] has the following aliases [%s]',self::LOGKEY,$o->name,$o->aliases->join(',')));
foreach ($o->aliases as $alias) {
$new_attr = clone $o;
@ -445,7 +449,7 @@ final class Server
return $this->attributetypes;
case 'ldapsyntaxes':
Log::debug('LDAP Syntaxes');
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
@ -458,7 +462,7 @@ final class Server
return $this->ldapsyntaxes;
case 'matchingrules':
Log::debug('Matching Rules');
Log::debug(sprintf('%s:Matching Rules',self::LOGKEY));
$this->matchingruleuse = collect();
foreach ($schema->{$item} as $line) {
@ -499,7 +503,7 @@ final class Server
return $this->matchingrules;
case 'objectclasses':
Log::debug('Object Classes');
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))

View File

@ -10,17 +10,18 @@ use Illuminate\Support\Collection;
use App\Classes\LDAP\Server;
class APIController extends Controller
class AjaxController 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() ?: collect();
$base = Server::baseDNs(TRUE) ?: collect();
return $base
->transform(fn($item)=>

View File

@ -5,41 +5,43 @@ namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
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
{
@ -58,17 +60,14 @@ class LoginController extends Controller
*/
public function logout(Request $request)
{
// Delete our LDAP authentication cookies
Cookie::queue(Cookie::forget('username_encrypt'));
Cookie::queue(Cookie::forget('password_encrypt'));
$user = Auth::user();
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) {
Log::info(sprintf('Logged out [%s]',$user->dn));
return $response;
}
@ -100,4 +99,4 @@ class LoginController extends Controller
{
return login_attr_name();
}
}
}

View File

@ -22,13 +22,12 @@ 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() ?: collect();
$base = Server::baseDNs(TRUE) ?: collect();
return $base->transform(function($item) {
return [
@ -45,7 +44,7 @@ class HomeController extends Controller
* Create a new object in the LDAP server
*
* @param EntryAddRequest $request
* @return View
* @return \Illuminate\View\View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
@ -90,15 +89,20 @@ 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);
return $request->noheader
$view = $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)
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),new: TRUE,edit: TRUE)
->render();
: view('components.attribute-type')
->with('new',TRUE)
->with('edit',TRUE);
return $view
->with('o',$o)
->with('langtag',Entry::TAG_NOTAG)
->with('updated',FALSE);
}
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
@ -189,8 +193,7 @@ class HomeController extends Controller
{
$dn = Crypt::decryptString($id);
$result = (new Entry)
->query()
$result = Entry::query()
->setDn($dn)
->recursive()
->get();
@ -351,7 +354,8 @@ 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())));
}
/**
@ -393,10 +397,10 @@ class HomeController extends Controller
->with('o',$o)
->with('page_actions',collect([
'copy'=>FALSE,
'create'=>TRUE,
'delete'=>TRUE,
'edit'=>TRUE,
'export'=>TRUE,
'create'=>($x=($o->getObjects()->except('entryuuid')->count() > 0)),
'delete'=>$x,
'edit'=>$x,
'export'=>$x,
])),
'import' => $view,

View File

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

View File

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

View File

@ -5,11 +5,12 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\Container;
use App\Ldap\Connection;
use App\Ldap\Guard;
class SwapinAuthUser
{
@ -28,25 +29,19 @@ 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')));
} 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')]);
Log::debug('Swapping out configured LDAP credentials with the user\'s session.',['key'=>$key]);
}
// 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.
Container::getInstance()->addConnection(new Connection(config('ldap.connections.'.$key)),$key);
$c = Container::getInstance()
->getConnection($key);
$c->setConfiguration(config('ldap.connections.'.$key));
$c->setGuardResolver(fn()=>new Guard($c->getLdapConnection(),$c->getConfiguration()));
return $next($request);
}

View File

@ -0,0 +1,30 @@
<?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);
}
}

View File

@ -1,21 +0,0 @@
<?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,8 +27,6 @@ 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;
@ -71,8 +69,6 @@ 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
{
@ -84,16 +80,6 @@ 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
@ -288,7 +274,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;
@ -453,9 +439,10 @@ class Entry extends Model
*/
public function icon(): string
{
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
$objectclasses = ($x=$this->getObject('objectclass'))
? $x->tagValues()
->map(fn($item)=>strtolower($item))
: collect();
// Return icon based upon objectClass value
if ($objectclasses->intersect([
@ -539,19 +526,6 @@ 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,26 +2,20 @@
namespace App\Ldap;
use Illuminate\Support\Facades\Cookie;
// use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use LdapRecord\Auth\Guard as GuardBase;
class Guard extends GuardBase
{
public function attempt(string $username, string $password, bool $stayBound = false): bool
{
if ($result = parent::attempt($username,$password,$stayBound)) {
/*
* 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));
*/
Log::info(sprintf('Attempting login for [%s] with password [%s]',$username,($password ? str_repeat('*',16) : str_repeat('?',16))));
// For our API calls, we store the cookie - which our cookies are already encrypted
Cookie::queue('username_encrypt',$username);
Cookie::queue('password_encrypt',$password);
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));
}
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() as $base) {
foreach (Server::baseDNs(FALSE) 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() as $base) {
foreach (Server::baseDNs(FALSE) as $base) {
$user = $this->query()->setBaseDn($base)->findByGuid($guid);
if ($user)

View File

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

View File

@ -2,7 +2,6 @@
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
@ -16,19 +15,18 @@ 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,?string $na=NULL)
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE)
{
$this->o = $o;
$this->edit = $edit;
$this->old = $old;
$this->new = $new;
$this->langtag = $langtag;
$this->na = $na;
$this->updated = $updated;
}
/**
@ -40,7 +38,7 @@ class Attribute extends Component
{
return $this->o
? $this->o
->render(edit: $this->edit,old: $this->old,new: $this->new)
: $this->na;
->render(edit: $this->edit,old: $this->old,new: $this->new,langtag: $this->langtag,updated: $this->updated)
: __('Unknown');
}
}

View File

@ -1,40 +0,0 @@
<?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,32 +1,27 @@
<?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};
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser,ViewVariables};
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('web', [
SwapinAuthUser::class,
ApplicationSession::class,
CheckUpdate::class,
]);
$middleware->prependToGroup('api', [
EncryptCookies::class,
SwapinAuthUser::class,
ApplicationSession::class,
AllowAnonymous::class,
]);
$middleware->appendToGroup(
group: 'web',
middleware: [
AllowAnonymous::class,
ApplicationSession::class,
SwapinAuthUser::class,
ViewVariables::class,
CheckUpdate::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.2",
"version": "v11.44.7",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
"reference": "00bc6ac91a6d577bf051c18ddaa638c0d221e1c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"url": "https://api.github.com/repos/laravel/framework/zipball/00bc6ac91a6d577bf051c18ddaa638c0d221e1c7",
"reference": "00bc6ac91a6d577bf051c18ddaa638c0d221e1c7",
"shasum": ""
},
"require": {
@ -1410,7 +1410,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-03-12T14:34:30+00:00"
"time": "2025-04-25T12:40:47+00:00"
},
{
"name": "laravel/prompts",
@ -1473,16 +1473,16 @@
},
{
"name": "laravel/sanctum",
"version": "v4.0.8",
"version": "v4.1.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c"
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
"shasum": ""
},
"require": {
@ -1533,7 +1533,7 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2025-01-26T19:34:36+00:00"
"time": "2025-04-23T13:03:38+00:00"
},
{
"name": "laravel/serializable-closure",
@ -1661,16 +1661,16 @@
},
{
"name": "league/commonmark",
"version": "2.6.1",
"version": "2.6.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94",
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94",
"shasum": ""
},
"require": {
@ -1764,7 +1764,7 @@
"type": "tidelift"
}
],
"time": "2024-12-29T14:10:59+00:00"
"time": "2025-04-18T21:09:27+00:00"
},
{
"name": "league/config",
@ -5856,16 +5856,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.15.3",
"version": "v3.15.4",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037"
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/4ccab20844d18c5af08b68d310e7151a791c3037",
"reference": "4ccab20844d18c5af08b68d310e7151a791c3037",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106",
"reference": "c0667ea91f7185f1e074402c5788195e96bf8106",
"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.3"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4"
},
"funding": [
{
@ -5937,7 +5937,7 @@
"type": "github"
}
],
"time": "2025-04-08T15:11:06+00:00"
"time": "2025-04-16T06:32:06+00:00"
},
{
"name": "fakerphp/faker",
@ -6075,20 +6075,20 @@
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.1",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
"reference": "99ec86beb7da3604d57cd3ca3699d2853f53018d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/99ec86beb7da3604d57cd3ca3699d2853f53018d",
"reference": "99ec86beb7da3604d57cd3ca3699d2853f53018d",
"shasum": ""
},
"require": {
"php": "^5.3|^7.0|^8.0"
"php": "^7.4|^8.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
@ -6096,8 +6096,8 @@
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "^1.4 || ^2.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
"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"
},
"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.0.1"
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.0"
},
"time": "2020-07-09T08:09:16+00:00"
"time": "2025-04-29T18:09:42+00:00"
},
{
"name": "mockery/mockery",
@ -6209,16 +6209,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.0",
"version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"shasum": ""
},
"require": {
@ -6257,7 +6257,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
},
"funding": [
{
@ -6265,7 +6265,7 @@
"type": "tidelift"
}
],
"time": "2025-02-12T12:17:51+00:00"
"time": "2025-04-29T12:36:36+00:00"
},
{
"name": "nikic/php-parser",
@ -6937,16 +6937,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.17",
"version": "11.5.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c"
"reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc3e887c7f3f9917e1bf61e523413d753db00a17",
"reference": "fc3e887c7f3f9917e1bf61e523413d753db00a17",
"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.17"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.18"
},
"funding": [
{
@ -7029,12 +7029,20 @@
"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-08T07:59:11+00:00"
"time": "2025-04-22T06:09:49+00:00"
},
{
"name": "sebastian/cli-parser",
@ -7964,16 +7972,16 @@
},
{
"name": "spatie/backtrace",
"version": "1.7.1",
"version": "1.7.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "0f2477c520e3729de58e061b8192f161c99f770b"
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b",
"reference": "0f2477c520e3729de58e061b8192f161c99f770b",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/9807de6b8fecfaa5b3d10650985f0348b02862b2",
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2",
"shasum": ""
},
"require": {
@ -8011,7 +8019,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/backtrace/tree/1.7.1"
"source": "https://github.com/spatie/backtrace/tree/1.7.2"
},
"funding": [
{
@ -8023,7 +8031,7 @@
"type": "other"
}
],
"time": "2024-12-02T13:28:15+00:00"
"time": "2025-04-28T14:55:53+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', 'debug'),
'level' => env('LOG_LEVEL', 'info'),
'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.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"version": "22.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
"integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@ -2730,9 +2730,9 @@
}
},
"node_modules/asn1.js/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/assert": {
@ -2804,9 +2804,9 @@
}
},
"node_modules/axios": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -2935,9 +2935,9 @@
}
},
"node_modules/bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
"license": "MIT"
},
"node_modules/body-parser": {
@ -3315,9 +3315,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001713",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz",
"integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==",
"version": "1.0.30001715",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
"integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
"funding": [
{
"type": "opencollective",
@ -3677,9 +3677,9 @@
}
},
"node_modules/core-js-compat": {
"version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
"integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==",
"version": "3.42.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz",
"integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4"
@ -3722,9 +3722,9 @@
}
},
"node_modules/create-ecdh/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/create-hash": {
@ -4158,9 +4158,9 @@
}
},
"node_modules/diffie-hellman/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/dir-glob": {
@ -4328,9 +4328,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
"version": "1.5.144",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.144.tgz",
"integrity": "sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -4349,9 +4349,9 @@
}
},
"node_modules/elliptic/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/emoji-regex": {
@ -4454,9 +4454,9 @@
}
},
"node_modules/es-module-lexer": {
"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==",
"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==",
"license": "MIT"
},
"node_modules/es-object-atoms": {
@ -6410,9 +6410,9 @@
}
},
"node_modules/miller-rabin/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/mime": {
@ -7782,9 +7782,9 @@
}
},
"node_modules/public-encrypt/node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/punycode": {
@ -8240,9 +8240,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.86.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz",
"integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
"version": "1.87.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
"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.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"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==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9431,13 +9431,14 @@
}
},
"node_modules/webpack": {
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"version": "5.99.7",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz",
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==",
"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",
@ -9454,7 +9455,7 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.0",
"schema-utils": "^4.3.2",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1",
@ -9581,9 +9582,9 @@
"license": "MIT"
},
"node_modules/webpack-dev-middleware/node_modules/schema-utils": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9693,9 +9694,9 @@
"license": "MIT"
},
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@ -9788,9 +9789,9 @@
"license": "MIT"
},
"node_modules/webpack/node_modules/schema-utils": {
"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==",
"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==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",

View File

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

13
public/css/custom.css vendored
View File

@ -23,6 +23,10 @@ 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;
@ -69,4 +73,13 @@ 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;
}

View File

@ -252,4 +252,9 @@ 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: 'api/bases' };
sources = { url: 'ajax/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: '/api/children',
url: '/ajax/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">
<div class="page-title-wrapper bg-white">
<div class="page-title-heading">
@if (trim($__env->yieldContent('page_icon')))
<div class="page-title-icon f32">
@ -13,8 +13,14 @@
</div>
</div>
<div class="page-title-actions">
@yield('page_actions')
<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>
</div>
</div>

View File

@ -1,4 +1,3 @@
<!-- $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">
@ -6,6 +5,9 @@
<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
@ -15,7 +17,7 @@
</div>
</div>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag"/>
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag" :updated="$updated"/>
</div>
</div>

View File

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

View File

@ -1,4 +1,4 @@
<!-- $o=Internal::class -->
<!-- $o=Attribute|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)]) 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),'bg-success-subtle'=>$updated]) 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" :loop="$loop" :value="$value" :langtag="$langtag"/>
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :langtag="$langtag" :updated="$updated" :value="$value" :loop="$loop" />
@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)]) 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),'bg-success-subtle'=>$updated]) 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)]) 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),'bg-success-subtle'=>$updated]) 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,6 +2,7 @@
@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)
@ -54,6 +55,9 @@
var rendered = false;
var newadded = [];
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
if (newadded.length)
process_oc();
@ -88,7 +92,7 @@
// Get a list of attributes already on the page, so we dont double up
$.ajax({
method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
// Render any must attributes
@ -153,7 +157,7 @@
$.ajax({
method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
var attrs = [];

View File

@ -1,11 +1,5 @@
@if($errors->any())
<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 class="alert alert-danger p-2">
<p class="m-0"><i class="fas fa-fw fa-thumbs-down"></i> @lang('Validation Errors')</p>
</div>
@endif

View File

@ -1,7 +1,5 @@
@if(session()->has('note'))
<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 class="alert alert-info p-2">
<p class="m-0"><i class="fas fa-fw fa-info"></i> {{ session()->pull('note') }}</p>
</div>
@endif

View File

@ -1,12 +1,5 @@
@if(session()->has('updated'))
<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 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>
@endif

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach($server->baseDNs()->sort(fn($item)=>$item->sort_key) as $item)
@foreach($server->baseDNs(TRUE)->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>{{ $o->getDn() }}</strong></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>
</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')" :na="__('Unknown')"/> [<x-attribute :o="$o->getObject('creatorsname')" :na="__('Unknown')"/>]
<x-attribute :o="$o->getObject('createtimestamp')"/> [<x-attribute :o="$o->getObject('creatorsname')"/>]
</th>
</tr>
<tr class="mt-1">
<td class="p-0 pe-2">Modified</td>
<th class="p-0">
<x-attribute :o="$o->getObject('modifytimestamp')" :na="__('Unknown')"/> [<x-attribute :o="$o->getObject('modifiersname')" :na="__('Unknown')"/>]
<x-attribute :o="$o->getObject('modifytimestamp')"/> [<x-attribute :o="$o->getObject('modifiersname')"/>]
</th>
</tr>
<tr class="mt-1">
<td class="p-0 pe-2">UUID</td>
<th class="p-0">
<x-attribute :o="$o->getObject('entryuuid')" :na="__('Unknown')"/>
<x-attribute :o="$o->getObject('entryuuid')"/>
</th>
</tr>
@if($langtags->count())

View File

@ -9,9 +9,11 @@
])
@endsection
@section('main-content')
@section('page_status')
<x-error/>
@endsection
@section('main-content')
<div class="row">
<div class="offset-1 col-10">
<div class="main-card mb-3 card">
@ -103,9 +105,10 @@
$(document).ready(function() {
@if($step === 2)
var oc = {!! $o->getObject('objectclass')->values !!};
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({
type: 'POST',
url: '{{ url('entry/attr/add') }}/'+item.target.value,

View File

@ -51,27 +51,25 @@
</div>
</div>
</div>
@endsection
<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
@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>
</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/>
@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">
@ -90,8 +88,14 @@
<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">
@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])>
@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>
@ -113,12 +117,13 @@
<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 :edit="true" :o="$ao" :langtag="$tag"/>
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :langtag="$tag" :updated="$up->contains($ao->name_lc)"/>
@endforeach
@break
@ -131,7 +136,7 @@
@default
@foreach ($o->getVisibleAttributes($langtag=sprintf('lang-%s',$tag)) as $ao)
<x-attribute-type :edit="true" :o="$ao" :langtag="$langtag"/>
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :langtag="$langtag" :updated="$up->contains($ao->name_lc)"/>
@endforeach
@endswitch
</div>
@ -153,7 +158,7 @@
<!-- Internal Attributes -->
<div class="tab-pane mt-3" id="internal" role="tabpanel">
@foreach ($o->getInternalAttributes() as $ao)
<x-attribute-type :o="$ao"/>
<x-attribute-type :o="$ao" :edit="FALSE" :new="FALSE" :langtag="Entry::TAG_NOTAG" :updated="FALSE"/>
@endforeach
</div>
</div>
@ -174,7 +179,6 @@
@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);
@ -225,6 +229,9 @@
});
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({
type: 'POST',
beforeSend: function() {},
@ -334,6 +341,11 @@
}
});
$('#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,11 +1,10 @@
@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
@ -58,7 +57,7 @@
return false;
$.ajax({
url: '{{ url('api/schema/view') }}',
url: '{{ url('ajax/schema/view') }}',
method: 'POST',
data: { type: type },
dataType: 'html',

View File

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

View File

@ -9,11 +9,11 @@
])
@endsection
@section('main-content')
<x-note/>
<x-success/>
@section('page_status')
<x-error/>
@endsection
@section('main-content')
<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())+1}}">
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())}}">
<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 }}[{{ collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo,$dotkey) }}"></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>
@endif
@endforeach
</tr>

View File

@ -1,23 +0,0 @@
<?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\HomeController;
use App\Http\Controllers\{AjaxController,HomeController};
use App\Http\Controllers\Auth\LoginController;
use App\Http\Middleware\AllowAnonymous;
@ -57,4 +57,13 @@ 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('/');
$response = $this->get('/login');
$response->assertStatus(200);
}

View File

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