Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
7346a3daf5 | |||
305ef0f5a3 | |||
f1316d698d | |||
339ba7258a | |||
883ac5d90f | |||
46277146c5 | |||
06747064d4 | |||
2c91298b41 | |||
9798863e34 | |||
4494154879 | |||
b22c9505bc | |||
29a659ff69 | |||
2348da36c4 | |||
6f58f5db36 | |||
553368c7b9 | |||
c8d1122ff6 | |||
2320445dfb | |||
6d2c9d1354 | |||
6f20d426ad | |||
7b1b4f4e50 | |||
543250e1fb | |||
3bf97fc0d1 | |||
3ad4c446ea | |||
ee3cb395c2 | |||
29c39e618f | |||
647cee9858 | |||
54c0df2597 | |||
67d65b3a98 | |||
9547b5fc5a | |||
f6b7bff605 | |||
e8aaa17122 | |||
ee7762d69b | |||
fac560750e | |||
d3aa73e468 | |||
2ddeff8ed3 | |||
b6bce380dd | |||
8fd2a43ee2 | |||
96afbd8316 | |||
5ce3a63878 | |||
ac8e79ab99 | |||
d0c02b91c0 | |||
2a691c147e | |||
781c87cb83 | |||
98a0b87afe | |||
88db4ccc99 | |||
6059bc1e45 | |||
acf19cdc5b | |||
56fcd729e7 | |||
d61f6168a4 | |||
f2eaed247a | |||
31e3c75bc9 | |||
9f0290bd40 | |||
820f398c2c | |||
8602c2b17f | |||
33d96940e6 | |||
06b7c204b0 | |||
7854cbdabd | |||
32514c9ab1 | |||
db600a28d3 | |||
b08de519d4 | |||
6599bb7f4f | |||
d623f3c26d | |||
bd40ab0e84 | |||
3fcb8707d9 |
@ -2,7 +2,6 @@ APP_NAME=Laravel
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
|
||||
@ -12,7 +11,7 @@ SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
LDAP_HOST=
|
||||
LDAP_BASE_DN=
|
||||
LDAP_USERNAME=
|
||||
LDAP_PASSWORD=
|
||||
LDAP_CACHE=false
|
||||
LDAP_ALERT_ROOTDN=true
|
||||
|
@ -2,7 +2,6 @@ APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=stderr
|
||||
|
||||
@ -12,7 +11,6 @@ SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
LDAP_HOST=openldap
|
||||
LDAP_BASE_DN="dc=Test"
|
||||
LDAP_USERNAME="cn=admin,dc=Test"
|
||||
LDAP_PASSWORD="test"
|
||||
LDAP_CACHE=false
|
||||
|
@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} Building Docker Image 🐳
|
||||
on: [push]
|
||||
env:
|
||||
DOCKER_HOST: tcp://127.0.0.1:2375
|
||||
ASSETS: 10eca55
|
||||
ASSETS: 2d732e5
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
55
README.md
55
README.md
@ -1,4 +1,10 @@
|
||||
# phpLDAPadmin
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
phpLDAPadmin is a web based LDAP data management tool for system administrators. It is commonly known and referred by many as "PLA".
|
||||
|
||||
PLA is designed to be compliant with LDAP RFCs, enabling it to be used with any LDAP server.
|
||||
@ -27,38 +33,33 @@ Take a look at the [Docker Container](https://github.com/leenooks/phpLDAPadmin/w
|
||||
>
|
||||
> Open an issue (details below) with enough information for me to be able to recreate the problem. An `LDIF` will be invaluable if it is not handling data correctly.
|
||||
|
||||
## Version 2 Progress
|
||||
## Templates
|
||||
Starting with v2.2, PLA reintroduces the template engine. Each point release going forward will improve the template
|
||||
functionality. Check [releases](https://github.com/leenooks/phpLDAPadmin/releases) for details.
|
||||
|
||||
The update to v2 is progressing well - here is a list of work to do and done:
|
||||
Templates in v2 are in JSON format (in v1 they were XML format). If you want to create your own templates you can use
|
||||
the [example.json](/templates/example.json) template as a guide. Place your custom templates in a subdirectory
|
||||
under `templates`, eg: `templates/custom`, and they wont be overwritten by an update.
|
||||
|
||||
- [X] Creating new LDAP entries
|
||||
- [X] Delete existing LDAP entries
|
||||
- [X] Updating existing LDAP Entries
|
||||
- [X] Password attributes
|
||||
- [X] Support different password hash options
|
||||
- [X] Validate password is correct
|
||||
## Outstanding items
|
||||
Compare to v1.x, there are a couple of outstanding items to address
|
||||
|
||||
Entry Editing:
|
||||
- [ ] JpegPhoto Create/Delete
|
||||
- [ ] Binary attribute upload
|
||||
- [X] JpegPhoto Display
|
||||
- [X] ObjectClass Add/Remove
|
||||
- [X] Add additional required attributes (for ObjectClass Addition)
|
||||
- [ ] Remove existing required attributes (for ObjectClass Removal)
|
||||
- [X] Add additional values to Attributes that support multiple values
|
||||
- [X] Delete extra values for Attributes that support multiple values
|
||||
- [ ] Delete Attributes
|
||||
- [ ] Templates to enable entries to conform to a custom standard
|
||||
- [ ] Autopopulate attribute values
|
||||
- [X] Login to LDAP server
|
||||
- [X] Configure login by a specific attribute
|
||||
- [X] Logout LDAP server
|
||||
- [X] Export entries as an LDAP
|
||||
- [X] Import LDIF
|
||||
- [X] Schema Browser
|
||||
- [X] Searching
|
||||
- [ ] Enforcing attribute uniqueness
|
||||
- [ ] Is there something missing?
|
||||
- [ ] If removing an objectClass, remove all attributes that only that objectclass provided
|
||||
- [ ] Move an entry
|
||||
- [ ] Group membership selection
|
||||
- [ ] Attribute tag creation
|
||||
|
||||
Support is known for these LDAP servers:
|
||||
Templates Engine
|
||||
- [ ] Enforcing attribute uniqueness
|
||||
|
||||
Raise a [feature request](https://github.com/leenooks/phpLDAPadmin/issues/new) if there is a capability that you would like to see added to PLA.
|
||||
|
||||
Other items [under consideration](https://github.com/leenooks/phpLDAPadmin/issues?q=state%3Aopen%20label%3Aenhancement)
|
||||
|
||||
## Support is known for these LDAP servers:
|
||||
- [X] OpenLDAP
|
||||
- [X] OpenDJ
|
||||
- [ ] Microsoft Active Directory
|
||||
|
@ -7,6 +7,8 @@ use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Schema\AttributeType;
|
||||
use App\Classes\Template;
|
||||
use App\Exceptions\InvalidUsage;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
@ -14,9 +16,6 @@ use App\Ldap\Entry;
|
||||
*/
|
||||
class Attribute implements \Countable, \ArrayAccess
|
||||
{
|
||||
// Attribute Name
|
||||
protected string $name;
|
||||
|
||||
// Is this attribute an internal attribute
|
||||
protected ?bool $_is_internal = NULL;
|
||||
protected(set) bool $no_attr_tags = FALSE;
|
||||
@ -98,11 +97,11 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
* @param string $name Name of the attribute
|
||||
* @param array $values Current Values
|
||||
* @param array $oc The objectclasses that the DN of this attribute has
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function __construct(string $dn,string $name,array $values,array $oc=[])
|
||||
{
|
||||
$this->dn = $dn;
|
||||
$this->name = $name;
|
||||
$this->_values = collect($values);
|
||||
$this->_values_old = collect($values);
|
||||
|
||||
@ -113,8 +112,12 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
|
||||
// Get the objectclass heirarchy for required attribute determination
|
||||
foreach ($oc as $objectclass) {
|
||||
$this->oc->push($objectclass);
|
||||
$this->oc = $this->oc->merge(config('server')->schema('objectclasses',$objectclass)->getParents()->pluck('name'));
|
||||
$soc = config('server')->schema('objectclasses',$objectclass);
|
||||
|
||||
if ($soc) {
|
||||
$this->oc->push($soc->oid);
|
||||
$this->oc = $this->oc->merge($soc->getParents()->pluck('oid'));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -140,14 +143,19 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
// List all the attributes
|
||||
'attributes' => $this->attributes(),
|
||||
// Can this attribute have more values
|
||||
'can_addvalues' => $this->schema && (! $this->schema->is_single_value) && ((! $this->max_values_count) || ($this->values->count() < $this->max_values_count)),
|
||||
// Schema attribute description
|
||||
'description' => $this->schema ? $this->schema->{$key} : NULL,
|
||||
// Attribute hints
|
||||
'hints' => $this->hints(),
|
||||
// Attribute language tags
|
||||
'langtags' => ($this->no_attr_tags || (! $this->_values->count()))
|
||||
? collect(Entry::TAG_NOTAG)
|
||||
: $this->_values
|
||||
->keys()
|
||||
->filter(fn($item)=>($item === Entry::TAG_NOTAG) || preg_match(sprintf('/%s;?/',Entry::TAG_CHARS_LANG),$item))
|
||||
->sortBy(fn($item)=>($item === Entry::TAG_NOTAG) ? NULL : $item),
|
||||
// Can this attribute be edited
|
||||
'is_editable' => $this->schema ? $this->schema->{$key} : NULL,
|
||||
// Is this an internal attribute
|
||||
@ -157,13 +165,15 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
// Is this attribute an RDN attribute
|
||||
'is_rdn' => $this->isRDN(),
|
||||
// We prefer the name as per the schema if it exists
|
||||
'name' => $this->schema ? $this->schema->{$key} : $this->{$key},
|
||||
'name' => $this->schema->{$key},
|
||||
// Attribute name in lower case
|
||||
'name_lc' => strtolower($this->name),
|
||||
// Required by Object Classes
|
||||
'required_by' => $this->schema?->required_by_object_classes ?: collect(),
|
||||
// Used in Object Classes
|
||||
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
|
||||
// For single value attributes
|
||||
'value' => $this->schema?->is_single_value ? $this->values->first() : NULL,
|
||||
// The current attribute values
|
||||
'values' => ($this->no_attr_tags || $this->is_internal) ? $this->tagValues() : $this->_values,
|
||||
// The original attribute values
|
||||
@ -198,17 +208,23 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->_values->dot()->count();
|
||||
return $this->_values
|
||||
->dot()
|
||||
->count();
|
||||
}
|
||||
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return $this->_values->dot()->has($offset);
|
||||
return $this->_values
|
||||
->dot()
|
||||
->has($offset);
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return $this->_values->dot()->get($offset);
|
||||
return $this->_values
|
||||
->dot()
|
||||
->get($offset);
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
@ -255,9 +271,6 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
if ($this->is_rdn)
|
||||
$result->put(__('rdn'),__('This attribute is required for the RDN'));
|
||||
|
||||
// If this attribute name is an alias for the schema attribute name
|
||||
// @todo
|
||||
|
||||
if ($this->required()->count())
|
||||
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', ')));
|
||||
|
||||
@ -289,7 +302,7 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
{
|
||||
return $this->schema->used_in_object_classes
|
||||
->keys()
|
||||
->intersect($this->schema->heirachy($this->oc))
|
||||
->intersect($this->oc)
|
||||
->count() === 0;
|
||||
}
|
||||
|
||||
@ -315,11 +328,11 @@ 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))
|
||||
* @param Template|null $template
|
||||
* @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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
if ($this->is_internal)
|
||||
// @note Internal attributes cannot be edited
|
||||
@ -340,7 +353,7 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new)
|
||||
->with('langtag',$langtag)
|
||||
->with('template',$template)
|
||||
->with('updated',$updated);
|
||||
}
|
||||
|
||||
@ -376,7 +389,7 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function required(): Collection
|
||||
private function required(): Collection
|
||||
{
|
||||
// If we dont have any objectclasses then we cant know if it is required
|
||||
return $this->oc->count()
|
||||
|
@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Binary;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Binary;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
/**
|
||||
@ -15,14 +15,13 @@ final class JpegPhoto extends Binary
|
||||
{
|
||||
use MD5Updates;
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.binary.jpegphoto')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new)
|
||||
->with('langtag',$langtag)
|
||||
->with('updated',$updated)
|
||||
->with('f',new \finfo);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Traits\MD5Updates;
|
||||
@ -17,6 +18,15 @@ final class Certificate extends Attribute
|
||||
|
||||
private array $_object = [];
|
||||
|
||||
public function authority_key_identifier(int $key=0): string
|
||||
{
|
||||
$data = collect(explode("\n",$this->cert_info('extensions.authorityKeyIdentifier',$key)));
|
||||
return $data
|
||||
->filter(fn($item)=>Str::startsWith($item,'keyid:'))
|
||||
->map(fn($item)=>Str::after($item,'keyid:'))
|
||||
->first();
|
||||
}
|
||||
|
||||
public function certificate(int $key=0): string
|
||||
{
|
||||
return sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----",
|
||||
@ -29,19 +39,23 @@ final class Certificate extends Attribute
|
||||
if (! array_key_exists($key,$this->_object))
|
||||
$this->_object[$key] = openssl_x509_parse(openssl_x509_read($this->certificate($key)));
|
||||
|
||||
|
||||
return Arr::get($this->_object[$key],$index);
|
||||
}
|
||||
|
||||
public function expires($key=0): Carbon
|
||||
public function expires(int $key=0): Carbon
|
||||
{
|
||||
return Carbon::createFromTimestampUTC($this->cert_info('validTo_time_t',$key));
|
||||
}
|
||||
|
||||
public function subject($key=0): string
|
||||
public function subject(int $key=0): string
|
||||
{
|
||||
$subject = collect($this->cert_info('subject',$key))->reverse();
|
||||
|
||||
return $subject->map(fn($item,$key)=>sprintf("%s=%s",$key,$item))->join(',');
|
||||
}
|
||||
|
||||
public function subject_key_identifier(int $key=0): string
|
||||
{
|
||||
return $this->cert_info('extensions.subjectKeyIdentifier',$key);
|
||||
}
|
||||
}
|
@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
@ -59,8 +58,6 @@ class Factory
|
||||
public static function create(string $dn,string $attribute,array $values,array $oc=[]): Attribute
|
||||
{
|
||||
$class = Arr::get(self::map,strtolower($attribute),Attribute::class);
|
||||
Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class));
|
||||
|
||||
return new $class($dn,$attribute,$values,$oc);
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@ namespace App\Classes\LDAP\Attribute\Internal;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* 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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
// @note Internal attributes cannot be edited
|
||||
return view('components.attribute.internal.timestamp')
|
||||
|
@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
/**
|
||||
@ -17,7 +17,7 @@ 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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.krbprincipalkey')
|
||||
->with('o',$this)
|
||||
|
@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose value is a Kerberos Ticket Flag
|
||||
@ -50,7 +50,7 @@ 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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.krbticketflags')
|
||||
->with('o',$this)
|
||||
|
@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* Represents an ObjectClass Attribute
|
||||
@ -70,7 +70,7 @@ 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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.objectclass')
|
||||
->with('o',$this)
|
||||
|
@ -7,7 +7,7 @@ use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
/**
|
||||
@ -80,13 +80,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,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.password')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new)
|
||||
->with('template',$template)
|
||||
->with('updated',$updated)
|
||||
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* Represents the RDN for an Entry
|
||||
@ -14,6 +14,9 @@ use App\Ldap\Entry;
|
||||
final class RDN extends Attribute
|
||||
{
|
||||
private string $base;
|
||||
|
||||
protected(set) bool $no_attr_tags = TRUE;
|
||||
|
||||
private Collection $attrs;
|
||||
|
||||
public function __get(string $key): mixed
|
||||
@ -32,7 +35,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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
return view('components.attribute.rdn')
|
||||
->with('o',$this);
|
||||
|
@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
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,11 +51,4 @@ abstract class Schema extends Attribute
|
||||
$key,
|
||||
__('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
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.internal')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@ namespace App\Classes\LDAP\Attribute\Schema;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Schema;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* 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,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.schema.generic')
|
||||
|
@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Schema;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* Represents a Mechanisms Attribute
|
||||
@ -34,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,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.schema.mechanisms')
|
||||
|
@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Schema;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
/**
|
||||
* Represents an OID Attribute
|
||||
@ -35,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,string $langtag=Entry::TAG_NOTAG,bool $updated=FALSE): View
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.schema.oid')
|
||||
|
@ -29,7 +29,7 @@ abstract class Export
|
||||
|
||||
abstract public function __toString(): string;
|
||||
|
||||
protected function header()
|
||||
protected function header(): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
@ -42,7 +42,7 @@ abstract class Export
|
||||
//$output .= sprintf('# %s: %s',__('Search Filter'),$this->entry->dn).$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Total Entries'),$this->items->count()).$this->br;
|
||||
$output .= '#'.$this->br;
|
||||
$output .= sprintf('# %s %s (%s) on %s',__('Generated by'),config('app.name'),config('app.url'),date('F j, Y g:i a')).$this->br;
|
||||
$output .= sprintf('# %s %s (%s) on %s',__('Generated by'),config('app.name'),request()->root(),date('F j, Y g:i a')).$this->br;
|
||||
$output .= sprintf('# %s %s',__('Exported by'),Auth::user() ?: 'Anonymous').$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Version'),config('app.version')).$this->br;
|
||||
|
||||
|
@ -7,304 +7,74 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Exceptions\InvalidUsage;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
* Represents an LDAP AttributeType
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class AttributeType extends Base {
|
||||
// The attribute from which this attribute inherits (if any)
|
||||
private ?string $sup_attribute = NULL;
|
||||
final class AttributeType extends Base
|
||||
{
|
||||
private const LOGKEY = 'SAT';
|
||||
|
||||
// Array of AttributeTypes which inherit from this one
|
||||
private Collection $children;
|
||||
// An array of AttributeTypes which inherit from this one
|
||||
private(set) Collection $children;
|
||||
|
||||
// The equality rule used
|
||||
private ?string $equality = NULL;
|
||||
|
||||
// The ordering of the attributeType
|
||||
private ?string $ordering = NULL;
|
||||
|
||||
// Supports substring matching?
|
||||
private ?string $sub_str_rule = NULL;
|
||||
|
||||
// The full syntax string, ie 1.2.3.4{16}
|
||||
private ?string $syntax = NULL;
|
||||
private ?string $syntax_oid = NULL;
|
||||
|
||||
// boolean: is single valued only?
|
||||
private bool $is_single_value = FALSE;
|
||||
|
||||
// boolean: is collective?
|
||||
private bool $is_collective = FALSE;
|
||||
|
||||
// boolean: can use modify?
|
||||
private bool $is_no_user_modification = FALSE;
|
||||
|
||||
// The usage string set by the LDAP schema
|
||||
private ?string $usage = NULL;
|
||||
|
||||
// An array of alias attribute names, strings
|
||||
private Collection $aliases;
|
||||
|
||||
// The max number of characters this attribute can be
|
||||
private ?int $max_length = NULL;
|
||||
|
||||
// A string description of the syntax type (taken from the LDAPSyntaxes)
|
||||
/**
|
||||
* @deprecated - reference syntaxes directly if possible
|
||||
* @var string
|
||||
*/
|
||||
private ?string $type = NULL;
|
||||
|
||||
// An array of objectClasses which use this attributeType (must be set by caller)
|
||||
private Collection $used_in_object_classes;
|
||||
|
||||
// A list of object class names that require this attribute type.
|
||||
private Collection $required_by_object_classes;
|
||||
private(set) ?string $equality = NULL;
|
||||
|
||||
// This attribute has been forced a MAY attribute by the configuration.
|
||||
private bool $forced_as_may = FALSE;
|
||||
private(set) bool $forced_as_may = FALSE;
|
||||
|
||||
/**
|
||||
* Creates a new AttributeType object from a raw LDAP AttributeType string.
|
||||
*
|
||||
* eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
|
||||
*/
|
||||
public function __construct(string $line) {
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('Parsing AttributeType [%s]',$line));
|
||||
// boolean: is collective?
|
||||
private(set) bool $is_collective = FALSE;
|
||||
|
||||
parent::__construct($line);
|
||||
// Is this a must attribute
|
||||
private(set) bool $is_must = FALSE;
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
// boolean: can use modify?
|
||||
private(set) bool $is_no_user_modification = FALSE;
|
||||
|
||||
// Init
|
||||
$this->children = collect();
|
||||
$this->aliases = collect();
|
||||
$this->used_in_object_classes = collect();
|
||||
$this->required_by_object_classes = collect();
|
||||
// boolean: is single valued only?
|
||||
private(set) bool $is_single_value = FALSE;
|
||||
|
||||
for ($i=0; $i < count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
// The max number of characters this attribute can be
|
||||
private(set) ?int $max_length = NULL;
|
||||
|
||||
case 'NAME':
|
||||
// @note Some schema's return a (' instead of a ( '
|
||||
if ($strings[$i+1] != '(' && ! preg_match('/^\(/',$strings[$i+1])) {
|
||||
do {
|
||||
$this->name .= ($this->name ? ' ' : '').$strings[++$i];
|
||||
// An array of names (including aliases) that this attribute is known by
|
||||
private(set) Collection $names;
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
// The ordering of the attributeType
|
||||
private(set) ?string $ordering = NULL;
|
||||
|
||||
// This attribute has no aliases
|
||||
//$this->aliases = collect();
|
||||
// A list of object class names that require this attribute type.
|
||||
private(set) Collection $required_by_object_classes;
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
// Which objectclass is defining this attribute for an Entry
|
||||
public ?string $source = NULL;
|
||||
|
||||
do {
|
||||
// In case we came here becaues of a ('
|
||||
if (preg_match('/^\(/',$strings[$i]))
|
||||
$strings[$i] = preg_replace('/^\(/','',$strings[$i]);
|
||||
else
|
||||
$i++;
|
||||
// Supports substring matching?
|
||||
private(set) ?string $sub_str_rule = NULL;
|
||||
|
||||
$this->name .= ($this->name ? ' ' : '').$strings[++$i];
|
||||
// The attribute from which this attribute inherits (if any)
|
||||
private(set) ?string $sup_attribute = NULL;
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
// The full syntax string, ie 1.2.3.4{16}
|
||||
private(set) ?string $syntax = NULL;
|
||||
private(set) ?string $syntax_oid = NULL;
|
||||
|
||||
// Add alias names for this attribute
|
||||
while ($strings[++$i] != ')') {
|
||||
$alias = $strings[$i];
|
||||
$alias = preg_replace("/^\'(.*)\'$/",'$1',$alias);
|
||||
$this->addAlias($alias);
|
||||
}
|
||||
}
|
||||
// The usage string set by the LDAP schema
|
||||
private(set) ?string $usage = NULL;
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case NAME returned (%s)',$this->name),['aliases'=>$this->aliases]);
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= ($this->description ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SUP':
|
||||
$i++;
|
||||
$this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_attribute));
|
||||
break;
|
||||
|
||||
case 'EQUALITY':
|
||||
$this->equality = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case EQUALITY returned (%s)',$this->equality));
|
||||
break;
|
||||
|
||||
case 'ORDERING':
|
||||
$this->ordering = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case ORDERING returned (%s)',$this->ordering));
|
||||
break;
|
||||
|
||||
case 'SUBSTR':
|
||||
$this->sub_str_rule = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUBSTR returned (%s)',$this->sub_str_rule));
|
||||
break;
|
||||
|
||||
case 'SYNTAX':
|
||||
$this->syntax = $strings[++$i];
|
||||
$this->syntax_oid = preg_replace('/{\d+}$/','',$this->syntax);
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('/ Evaluating SYNTAX returned (%s) [%s]',$this->syntax,$this->syntax_oid));
|
||||
|
||||
// Does this SYNTAX string specify a max length (ie, 1.2.3.4{16})
|
||||
$m = [];
|
||||
if (preg_match('/{(\d+)}$/',$this->syntax,$m))
|
||||
$this->max_length = $m[1];
|
||||
else
|
||||
$this->max_length = NULL;
|
||||
|
||||
if ($i < count($strings) - 1 && $strings[$i+1] == '{')
|
||||
do {
|
||||
$this->name .= ' '.$strings[++$i];
|
||||
} while ($strings[$i] != '}');
|
||||
|
||||
$this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax);
|
||||
$this->syntax_oid = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax_oid);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SYNTAX returned (%s) [%s] {%d}',$this->syntax,$this->syntax_oid,$this->max_length));
|
||||
break;
|
||||
|
||||
case 'SINGLE-VALUE':
|
||||
$this->is_single_value = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SINGLE-VALUE returned (%s)',$this->is_single_value));
|
||||
break;
|
||||
|
||||
case 'COLLECTIVE':
|
||||
$this->is_collective = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case COLLECTIVE returned (%s)',$this->is_collective));
|
||||
break;
|
||||
|
||||
case 'NO-USER-MODIFICATION':
|
||||
$this->is_no_user_modification = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case NO-USER-MODIFICATION returned (%s)',$this->is_no_user_modification));
|
||||
break;
|
||||
|
||||
case 'USAGE':
|
||||
$this->usage = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case USAGE returned (%s)',$this->usage));
|
||||
break;
|
||||
|
||||
// @note currently not captured
|
||||
case 'X-ORDERED':
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::error(sprintf('- Case X-ORDERED returned (%s)',$strings[++$i]));
|
||||
break;
|
||||
|
||||
// @note currently not captured
|
||||
case 'X-ORIGIN':
|
||||
$value = '';
|
||||
|
||||
do {
|
||||
$value .= ($value ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::error(sprintf('- Case X-ORIGIN returned (%s)',$value));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// When we clone, we need to break the reference too
|
||||
$this->aliases = clone $this->aliases;
|
||||
}
|
||||
// An array of objectClasses which use this attributeType (must be set by caller)
|
||||
private(set) Collection $used_in_object_classes;
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'aliases': return $this->aliases;
|
||||
case 'children': return $this->children;
|
||||
case 'forced_as_may': return $this->forced_as_may;
|
||||
case 'is_collective': return $this->is_collective;
|
||||
case 'is_editable': return ! $this->is_no_user_modification;
|
||||
case 'is_no_user_modification': return $this->is_no_user_modification;
|
||||
case 'is_single_value': return $this->is_single_value;
|
||||
case 'equality': return $this->equality;
|
||||
case 'max_length': return $this->max_length;
|
||||
case 'ordering': return $this->ordering;
|
||||
case 'required_by_object_classes': return $this->required_by_object_classes;
|
||||
case 'sub_str_rule': return $this->sub_str_rule;
|
||||
case 'sup_attribute': return $this->sup_attribute;
|
||||
case 'syntax': return $this->syntax;
|
||||
case 'syntax_oid': return $this->syntax_oid;
|
||||
case 'type': return $this->type;
|
||||
case 'usage': return $this->usage;
|
||||
case 'used_in_object_classes': return $this->used_in_object_classes;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute name to the alias array.
|
||||
*
|
||||
* @param string $alias The name of a new attribute to add to this attribute's list of aliases.
|
||||
*/
|
||||
public function addAlias(string $alias): void
|
||||
{
|
||||
$this->aliases->push($alias);
|
||||
return match ($key) {
|
||||
'names_lc' => $this->names->map('strtolower'),
|
||||
default => parent::__get($key)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,7 +85,8 @@ final class AttributeType extends Base {
|
||||
*/
|
||||
public function addChild(string $child): void
|
||||
{
|
||||
$this->children->push($child);
|
||||
$this->children
|
||||
->push($child);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -346,144 +117,10 @@ final class AttributeType extends Base {
|
||||
|
||||
private function factory(): Attribute
|
||||
{
|
||||
return Attribute\Factory::create(dn:'',attribute:$this->name,values:[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of attributes that are an alias for this attribute (if any).
|
||||
*
|
||||
* @return Collection An array of names of attributes which alias this attribute or
|
||||
* an empty array if no attribute aliases this object.
|
||||
* @deprecated use class->aliases
|
||||
*/
|
||||
public function getAliases(): Collection
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's equality string
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->equality
|
||||
*/
|
||||
public function getEquality()
|
||||
{
|
||||
return $this->equality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is collective.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is collective and FALSE otherwise.
|
||||
* @deprecated use $this->is_collective
|
||||
*/
|
||||
public function getIsCollective(): bool
|
||||
{
|
||||
return $this->is_collective;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is not modifiable by users.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is not modifiable by users.
|
||||
* @deprecated use $this->is_no_user_modification
|
||||
*/
|
||||
public function getIsNoUserModification(): bool
|
||||
{
|
||||
return $this->is_no_user_modification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is single-valued. If this attribute only supports single values, TRUE
|
||||
* is returned. If this attribute supports multiple values, FALSE is returned.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is single-valued or FALSE otherwise.
|
||||
* @deprecated use class->is_single_value
|
||||
*/
|
||||
public function getIsSingleValue(): bool
|
||||
{
|
||||
return $this->is_single_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's the maximum length. If no maximum is defined by the LDAP server, NULL is returned.
|
||||
*
|
||||
* @return int The maximum length (in characters) of this attribute or NULL if no maximum is specified.
|
||||
* @deprecated use $this->max_length;
|
||||
*/
|
||||
public function getMaxLength()
|
||||
{
|
||||
return $this->max_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's ordering specification.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->ordering
|
||||
*/
|
||||
public function getOrdering(): string
|
||||
{
|
||||
return $this->ordering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's substring matching specification
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->sub_str_rule;
|
||||
*/
|
||||
public function getSubstr() {
|
||||
return $this->sub_str_rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's parent attribute (if any). If this attribute does not
|
||||
* inherit from another attribute, NULL is returned.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $class->sup_attribute directly
|
||||
*/
|
||||
public function getSupAttribute() {
|
||||
return $this->sup_attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's syntax OID. Differs from getSyntaxString() in that this
|
||||
* function only returns the actual OID with any length specification removed.
|
||||
* Ie, if the syntax string is "1.2.3.4{16}", this function only retruns
|
||||
* "1.2.3.4".
|
||||
*
|
||||
* @return string The syntax OID string.
|
||||
* @deprecated use $this->syntax_oid;
|
||||
*/
|
||||
public function getSyntaxOID()
|
||||
{
|
||||
return $this->syntax_oid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's usage string as defined by the LDAP server
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->usage
|
||||
*/
|
||||
public function getUsage()
|
||||
{
|
||||
return $this->usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of "used in" objectClasses, that is the list of objectClasses
|
||||
* which provide this attribute.
|
||||
*
|
||||
* @return Collection An array of names of objectclasses (strings) which provide this attribute
|
||||
* @deprecated use $this->used_in_object_classes
|
||||
*/
|
||||
public function getUsedInObjectClasses(): Collection
|
||||
{
|
||||
return $this->used_in_object_classes;
|
||||
return Attribute\Factory::create(
|
||||
dn:'',
|
||||
attribute:$this->name,
|
||||
values:[]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -492,58 +129,162 @@ final class AttributeType extends Base {
|
||||
* @param Collection $ocs
|
||||
* @return Collection
|
||||
*/
|
||||
public function heirachy(Collection $ocs): Collection
|
||||
private function heirachy(Collection $ocs): Collection
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
foreach ($ocs as $oc) {
|
||||
$schema = config('server')
|
||||
->schema('objectclasses',$oc)
|
||||
->getParents(TRUE)
|
||||
->pluck('name');
|
||||
$item = config('server')
|
||||
->schema('objectclasses',$oc);
|
||||
|
||||
$result = $result->merge($schema)->push($oc);
|
||||
$result = $result
|
||||
->merge($item
|
||||
->getParents(TRUE)
|
||||
->pluck('oid'))
|
||||
->push($item->oid);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @deprecated use $this->forced_as_may
|
||||
*/
|
||||
public function isForceMay(): bool
|
||||
{
|
||||
return $this->forced_as_may;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an attribute name from this attribute's alias array.
|
||||
* Creates a new AttributeType object from a raw LDAP AttributeType string.
|
||||
*
|
||||
* @param string $alias The name of the attribute to remove.
|
||||
* eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
|
||||
*/
|
||||
public function removeAlias(string $alias): void
|
||||
protected function parse(string $line): void
|
||||
{
|
||||
if (($x=$this->aliases->search($alias)) !== FALSE)
|
||||
$this->aliases->forget($x);
|
||||
Log::debug(sprintf('%s:Parsing AttributeType [%s]',self::LOGKEY,$line));
|
||||
|
||||
// Init
|
||||
$this->names = collect();
|
||||
$this->children = collect();
|
||||
$this->used_in_object_classes = collect();
|
||||
$this->required_by_object_classes = collect();
|
||||
|
||||
parent::parse($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's list of aliases.
|
||||
*
|
||||
* @param Collection $aliases The array of alias names (strings)
|
||||
* @deprecated use $this->aliases =
|
||||
*/
|
||||
public function setAliases(Collection $aliases): void
|
||||
protected function parse_chunk(array $strings,int &$i): void
|
||||
{
|
||||
$this->aliases = $aliases;
|
||||
}
|
||||
switch ($strings[$i]) {
|
||||
case 'NAME':
|
||||
$name = '';
|
||||
|
||||
/**
|
||||
* This function will mark this attribute as a forced MAY attribute
|
||||
*/
|
||||
public function setForceMay() {
|
||||
$this->forced_as_may = TRUE;
|
||||
// @note Some schema's return a (' instead of a ( '
|
||||
// @note This attribute format has no aliases
|
||||
if ($strings[$i+1] !== '(' && ! preg_match('/^\(/',$strings[$i+1])) {
|
||||
do {
|
||||
$name .= ($name ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
// In case we came here because of a ('
|
||||
if (preg_match('/^\(/',$strings[$i]))
|
||||
$strings[$i] = preg_replace('/^\(/','',$strings[$i]);
|
||||
else
|
||||
$i++;
|
||||
|
||||
$name .= ($name ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
// Add alias names for this attribute
|
||||
while ($strings[++$i] !== ')') {
|
||||
$alias = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]);
|
||||
$this->names->push($alias);
|
||||
}
|
||||
}
|
||||
|
||||
$this->names = $this->names->push(preg_replace("/^\'(.*)\'$/",'$1',$name))->sort();
|
||||
$this->forced_as_may = $this->names_lc
|
||||
->intersect(array_map('strtolower',config('pla.force_may',[])))
|
||||
->count() > 0;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case NAME returned (%s)',self::LOGKEY,$this->name),['names'=>$this->names]);
|
||||
break;
|
||||
|
||||
case 'SUP':
|
||||
$this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[++$i]);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case SUP returned (%s)',self::LOGKEY,$this->sup_attribute));
|
||||
break;
|
||||
|
||||
case 'EQUALITY':
|
||||
$this->equality = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case EQUALITY returned (%s)',self::LOGKEY,$this->equality));
|
||||
break;
|
||||
|
||||
case 'ORDERING':
|
||||
$this->ordering = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case ORDERING returned (%s)',self::LOGKEY,$this->ordering));
|
||||
break;
|
||||
|
||||
case 'SUBSTR':
|
||||
$this->sub_str_rule = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case SUBSTR returned (%s)',self::LOGKEY,$this->sub_str_rule));
|
||||
break;
|
||||
|
||||
case 'SYNTAX':
|
||||
$this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$strings[++$i]);
|
||||
$this->syntax_oid = preg_replace("/^\'?(.*){\d+}\'?$/",'$1',$this->syntax);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:/ Evaluating SYNTAX returned (%s) [%s]',self::LOGKEY,$this->syntax,$this->syntax_oid));
|
||||
|
||||
// Does this SYNTAX string specify a max length (ie, 1.2.3.4{16})
|
||||
$m = [];
|
||||
$this->max_length = preg_match('/{(\d+)}$/',$this->syntax,$m)
|
||||
? $m[1]
|
||||
: NULL;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case SYNTAX returned (%s) [%s] {%d}',self::LOGKEY,$this->syntax,$this->syntax_oid,$this->max_length));
|
||||
break;
|
||||
|
||||
case 'SINGLE-VALUE':
|
||||
$this->is_single_value = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case SINGLE-VALUE returned (%s)',self::LOGKEY,$this->is_single_value));
|
||||
break;
|
||||
|
||||
case 'COLLECTIVE':
|
||||
$this->is_collective = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case COLLECTIVE returned (%s)',self::LOGKEY,$this->is_collective));
|
||||
break;
|
||||
|
||||
case 'NO-USER-MODIFICATION':
|
||||
$this->is_no_user_modification = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case NO-USER-MODIFICATION returned (%s)',self::LOGKEY,$this->is_no_user_modification));
|
||||
break;
|
||||
|
||||
case 'USAGE':
|
||||
$this->usage = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case USAGE returned (%s)',self::LOGKEY,$this->usage));
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::parse_chunk($strings,$i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,13 +298,28 @@ final class AttributeType extends Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's SUP attribute (ie, the attribute from which this attribute inherits).
|
||||
* If this is a MUST attribute to the objectclass that defines it
|
||||
*
|
||||
* @param string $attr The name of the new parent (SUP) attribute
|
||||
* @return void
|
||||
*/
|
||||
public function setSupAttribute(string $attr): void
|
||||
public function setMust(): void
|
||||
{
|
||||
$this->sup_attribute = trim($attr);
|
||||
$this->is_must = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's name.
|
||||
*
|
||||
* @param string $name The new name to give this attribute.
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function setName(string $name): void
|
||||
{
|
||||
// Quick validation
|
||||
if ($this->names_lc->count() && (! $this->names_lc->contains(strtolower($name))))
|
||||
throw new InvalidUsage(sprintf('Cannot set attribute name to [%s], its not an alias for [%s]',$name,$this->names->join(',')));
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Exceptions\InvalidUsage;
|
||||
|
||||
/**
|
||||
@ -10,38 +12,38 @@ use App\Exceptions\InvalidUsage;
|
||||
* A schema item is an ObjectClass, an AttributeBype, a MatchingRule, or a Syntax.
|
||||
* All schema items have at least two things in common: An OID and a Description.
|
||||
*/
|
||||
abstract class Base {
|
||||
abstract class Base
|
||||
{
|
||||
private const LOGKEY = 'Sb-';
|
||||
|
||||
protected const DEBUG_VERBOSE = FALSE;
|
||||
|
||||
// Record the LDAP String
|
||||
private string $line;
|
||||
private(set) string $line;
|
||||
|
||||
// The schema item's name.
|
||||
protected string $name = '';
|
||||
protected(set) string $name = '';
|
||||
|
||||
// The OID of this schema item.
|
||||
protected string $oid = '';
|
||||
protected(set) string $oid = '';
|
||||
|
||||
# The description of this schema item.
|
||||
protected string $description = '';
|
||||
protected(set) string $description = '';
|
||||
|
||||
// Boolean value indicating whether this objectClass is obsolete
|
||||
private bool $is_obsolete = FALSE;
|
||||
private(set) bool $is_obsolete = FALSE;
|
||||
|
||||
public function __construct(string $line)
|
||||
{
|
||||
$this->line = $line;
|
||||
|
||||
$this->parse($line);
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'description': return $this->description;
|
||||
case 'is_obsolete': return $this->is_obsolete;
|
||||
case 'line': return $this->line;
|
||||
case 'name': return $this->name;
|
||||
case 'name_lc': return strtolower($this->name);
|
||||
case 'oid': return $this->oid;
|
||||
|
||||
default:
|
||||
throw new InvalidUsage('Unknown key:'.$key);
|
||||
@ -54,69 +56,95 @@ abstract class Base {
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated replace with $class->description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this item is flagged as obsolete by the LDAP server.
|
||||
*
|
||||
* @deprecated replace with $this->is_obsolete
|
||||
*/
|
||||
public function getIsObsolete(): bool
|
||||
{
|
||||
return $this->is_obsolete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objects name.
|
||||
*
|
||||
* @param boolean $lower Return the name in lower case (default)
|
||||
* @return string The name
|
||||
* @deprecated use object->name
|
||||
*/
|
||||
public function getName(bool $lower=TRUE): string
|
||||
{
|
||||
return $lower ? strtolower($this->name) : $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objects name.
|
||||
*
|
||||
* @return string The name
|
||||
* @deprecated use object->oid
|
||||
*/
|
||||
public function getOID(): string
|
||||
{
|
||||
return $this->oid;
|
||||
}
|
||||
|
||||
public function setDescription(string $desc): void
|
||||
protected function parse(string $line): void
|
||||
{
|
||||
$this->description = $desc;
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
for ($i=0; $i < count($strings); $i++) {
|
||||
$this->parse_chunk($strings,$i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's name.
|
||||
*
|
||||
* @param string $name The new name to give this attribute.
|
||||
*/
|
||||
public function setName($name): void
|
||||
protected function parse_chunk(array $strings,int &$i): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
public function setOID(string $oid): void
|
||||
{
|
||||
$this->oid = $oid;
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] !== '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case NAME returned (%s)',self::LOGKEY,$this->name));
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case DESC returned (%s)',self::LOGKEY,$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case OBSOLETE returned (%s)',self::LOGKEY,$this->is_obsolete));
|
||||
break;
|
||||
|
||||
// @note currently not captured
|
||||
case 'X-SUBST':
|
||||
case 'X-ORDERED':
|
||||
case 'X-EQUALITY':
|
||||
case 'X-ORIGIN':
|
||||
$value = '';
|
||||
|
||||
do {
|
||||
$value .= ($value ? ' ' : '').preg_replace('/^\'(.+)\'$/','$1',$strings[++$i]);
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case [%s] returned (%s) - IGNORED',self::LOGKEY,$strings[$i],$value));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case default returned OID (%s)',self::LOGKEY,$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('%s:! Case default discovered a value NOT parsed (%s)',self::LOGKEY,$strings[$i]));
|
||||
}
|
||||
}
|
||||
}
|
@ -6,74 +6,49 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP Syntax
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class LDAPSyntax extends Base {
|
||||
final class LDAPSyntax extends Base
|
||||
{
|
||||
private const LOGKEY = 'SLS';
|
||||
|
||||
// Is human readable?
|
||||
private ?bool $is_not_human_readable = NULL;
|
||||
private(set) ?bool $is_not_human_readable = NULL;
|
||||
|
||||
// Binary transfer required?
|
||||
private ?bool $binary_transfer_required = NULL;
|
||||
private(set) ?bool $binary_transfer_required = NULL;
|
||||
|
||||
/**
|
||||
* Creates a new Syntax object from a raw LDAP syntax string.
|
||||
*/
|
||||
public function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing LDAPSyntax [%s]',$line));
|
||||
protected function parse(string $line): void
|
||||
{
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:Parsing LDAPSyntax [%s]',self::LOGKEY,$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
parent::parse($line);
|
||||
}
|
||||
|
||||
protected function parse_chunk(array $strings,int &$i): void
|
||||
{
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'X-BINARY-TRANSFER-REQUIRED':
|
||||
$this->binary_transfer_required = (str_replace("'",'',$strings[++$i]) === 'TRUE');
|
||||
|
||||
Log::debug(sprintf('- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',$this->binary_transfer_required));
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',self::LOGKEY,$this->binary_transfer_required));
|
||||
break;
|
||||
|
||||
case 'X-NOT-HUMAN-READABLE':
|
||||
$this->is_not_human_readable = (str_replace("'",'',$strings[++$i]) === 'TRUE');
|
||||
|
||||
Log::debug(sprintf('- Case X-NOT-HUMAN-READABLE returned (%s)',$this->is_not_human_readable));
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case X-NOT-HUMAN-READABLE returned (%s)',self::LOGKEY,$this->is_not_human_readable));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
parent::parse_chunk($strings,$i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'binary_transfer_required': return $this->binary_transfer_required;
|
||||
case 'is_not_human_readable': return $this->is_not_human_readable;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,106 +7,16 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP MatchingRule
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class MatchingRule extends Base {
|
||||
final class MatchingRule extends Base
|
||||
{
|
||||
private const LOGKEY = 'SMR';
|
||||
|
||||
// This rule's syntax OID
|
||||
private ?string $syntax = NULL;
|
||||
private(set) ?string $syntax = NULL;
|
||||
|
||||
// An array of attribute names who use this MatchingRule
|
||||
private Collection $used_by_attrs;
|
||||
|
||||
/**
|
||||
* Creates a new MatchingRule object from a raw LDAP MatchingRule string.
|
||||
*/
|
||||
function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing MatchingRule [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->used_by_attrs = collect();
|
||||
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'/",'',$this->name);
|
||||
$this->name = preg_replace("/\'$/",'',$this->name);
|
||||
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SYNTAX':
|
||||
$this->syntax = $strings[++$i];
|
||||
|
||||
Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'syntax': return $this->syntax;
|
||||
case 'used_by_attrs': return $this->used_by_attrs;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
private(set) Collection $used_by_attrs;
|
||||
|
||||
/**
|
||||
* Adds an attribute name to the list of attributes who use this MatchingRule
|
||||
@ -120,23 +30,33 @@ final class MatchingRule extends Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) which use this MatchingRule
|
||||
* Creates a new MatchingRule object from a raw LDAP MatchingRule string.
|
||||
*
|
||||
* @return array The array of attribute names (strings).
|
||||
* @deprecated use $this->used_by_attrs
|
||||
* @param string $line
|
||||
* @return void
|
||||
*/
|
||||
public function getUsedByAttrs()
|
||||
protected function parse(string $line): void
|
||||
{
|
||||
return $this->used_by_attrs;
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:Parsing MatchingRule [%s]',self::LOGKEY,$line));
|
||||
|
||||
// Init
|
||||
$this->used_by_attrs = collect();
|
||||
|
||||
parent::parse($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of used_by_attrs to the array specified by $attrs;
|
||||
*
|
||||
* @param Collection $attrs The array of attribute names (strings) which use this MatchingRule
|
||||
*/
|
||||
public function setUsedByAttrs(Collection $attrs): void
|
||||
protected function parse_chunk(array $strings,int &$i): void
|
||||
{
|
||||
$this->used_by_attrs = $attrs;
|
||||
switch ($strings[$i]) {
|
||||
case 'SYNTAX':
|
||||
$this->syntax = $strings[++$i];
|
||||
|
||||
Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax));
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::parse_chunk($strings,$i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP schema matchingRuleUse entry
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class MatchingRuleUse extends Base {
|
||||
// An array of attribute names who use this MatchingRule
|
||||
private Collection $used_by_attrs;
|
||||
|
||||
function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing MatchingRuleUse [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->used_by_attrs = collect();
|
||||
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'APPLIES':
|
||||
if ($strings[$i+1] != '(') {
|
||||
// Has a single attribute name
|
||||
$this->used_by_attrs = collect($strings[++$i]);
|
||||
|
||||
} else {
|
||||
// Has multiple attribute names
|
||||
while ($strings[++$i] != ')') {
|
||||
$new_attr = $strings[++$i];
|
||||
$new_attr = preg_replace("/^\'(.*)\'$/",'$1',$new_attr);
|
||||
|
||||
$this->used_by_attrs->push($new_attr);
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug(sprintf('- Case APPLIES returned (%s)',$this->used_by_attrs->join(',')));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) which use this MatchingRuleUse object.
|
||||
*
|
||||
* @return array The array of attribute names (strings).
|
||||
* @deprecated use $this->used_by_attrs
|
||||
*/
|
||||
public function getUsedByAttrs()
|
||||
{
|
||||
return $this->used_by_attrs;
|
||||
}
|
||||
}
|
@ -10,206 +10,28 @@ use App\Exceptions\InvalidUsage;
|
||||
|
||||
/**
|
||||
* Represents an LDAP Schema objectClass
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class ObjectClass extends Base
|
||||
{
|
||||
private const LOGKEY = 'SOC';
|
||||
|
||||
// Array of objectClasses which inherit from this one
|
||||
private(set) Collection $child_classes;
|
||||
|
||||
// Array of objectClass names from which this objectClass inherits
|
||||
private Collection $sup_classes;
|
||||
private(set) Collection $sup_classes;
|
||||
|
||||
// One of STRUCTURAL, ABSTRACT, or AUXILIARY
|
||||
private int $type;
|
||||
|
||||
// Arrays of attribute names that this objectClass requires
|
||||
private Collection $must_attrs;
|
||||
|
||||
// Arrays of attribute names that this objectClass allows, but does not require
|
||||
private Collection $may_attrs;
|
||||
|
||||
// Arrays of attribute names that this objectClass has been forced to MAY attrs, due to configuration
|
||||
private Collection $may_force;
|
||||
|
||||
// Array of objectClasses which inherit from this one
|
||||
private Collection $child_objectclasses;
|
||||
|
||||
private bool $is_obsolete;
|
||||
|
||||
/**
|
||||
* Creates a new ObjectClass object given a raw LDAP objectClass string.
|
||||
*
|
||||
* eg: ( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )
|
||||
*
|
||||
* @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?
|
||||
*/
|
||||
public function __construct(string $line,Server $server)
|
||||
{
|
||||
parent::__construct($line);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('Parsing ObjectClass [%s]',$line));
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->may_attrs = collect();
|
||||
$this->may_force = collect();
|
||||
$this->must_attrs = collect();
|
||||
$this->sup_classes = collect();
|
||||
$this->child_objectclasses = collect();
|
||||
|
||||
for ($i=0; $i < count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SUP':
|
||||
if ($strings[$i+1] != '(') {
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[++$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
if ($strings[$i] != '$')
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[$i]));
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i+1]));
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_classes->join(',')));
|
||||
break;
|
||||
|
||||
case 'ABSTRACT':
|
||||
$this->type = Server::OC_ABSTRACT;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case ABSTRACT returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'STRUCTURAL':
|
||||
$this->type = Server::OC_STRUCTURAL;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case STRUCTURAL returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'AUXILIARY':
|
||||
$this->type = Server::OC_AUXILIARY;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case AUXILIARY returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'MUST':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('= parseList returned %d (%s)',$i,$attrs->join(',')));
|
||||
|
||||
foreach ($attrs as $string) {
|
||||
$attr = new ObjectClassAttribute($string,$this->name);
|
||||
|
||||
if ($server->isForceMay($attr->getName())) {
|
||||
$this->may_force->push($attr);
|
||||
$this->may_attrs->push($attr);
|
||||
|
||||
} else
|
||||
$this->must_attrs->push($attr);
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case MUST returned (%s) (%s)',$this->must_attrs->join(','),$this->may_force->join(',')));
|
||||
break;
|
||||
|
||||
case 'MAY':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('parseList returned %d (%s)',$i,$attrs->join(',')));
|
||||
|
||||
foreach ($attrs as $string) {
|
||||
$attr = new ObjectClassAttribute($string,$this->name);
|
||||
$this->may_attrs->push($attr);
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case MAY returned (%s)',$this->may_attrs->join(',')));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attributes that this objectclass defines
|
||||
private(set) Collection $attributes;
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'attributes' => $this->getAllAttrs(TRUE),
|
||||
'sup' => $this->sup_classes,
|
||||
'all_attributes' => $this->getMustAttrs(TRUE)
|
||||
->merge($this->getMayAttrs(TRUE)),
|
||||
'type_name' => match ($this->type) {
|
||||
Server::OC_STRUCTURAL => 'Structural',
|
||||
Server::OC_ABSTRACT => 'Abstract',
|
||||
@ -220,23 +42,6 @@ final class ObjectClass extends Base
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of attributes that this objectClass provides
|
||||
*
|
||||
* @param bool $parents
|
||||
* @return Collection
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function getAllAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMustAttrs($parents)
|
||||
->transform(function($item) {
|
||||
$item->required = true;
|
||||
return $item;
|
||||
})
|
||||
->merge($this->getMayAttrs($parents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an objectClass to the list of objectClasses that inherit
|
||||
* from this objectClass.
|
||||
@ -245,57 +50,8 @@ final class ObjectClass extends Base
|
||||
*/
|
||||
public function addChildObjectClass(string $name): void
|
||||
{
|
||||
if (! $this->child_objectclasses->contains($name))
|
||||
$this->child_objectclasses->push($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of objectClass names which inherit from this objectClass.
|
||||
*
|
||||
* @return Collection Names of objectClasses which inherit from this objectClass.
|
||||
* @deprecated use $this->child_objectclasses
|
||||
*/
|
||||
public function getChildObjectClasses(): Collection
|
||||
{
|
||||
return $this->child_objectclasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to addMustAttrs, but it operates on the MAY
|
||||
* attributes of this objectClass.
|
||||
*
|
||||
* @param array $attr An array of attribute names (strings) to add.
|
||||
*/
|
||||
private function addMayAttrs(array $attr): void
|
||||
{
|
||||
if (! is_array($attr) || ! count($attr))
|
||||
return;
|
||||
|
||||
$this->may_attrs = $this->may_attrs->merge($attr)->unique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified array of attributes to this objectClass' list of
|
||||
* MUST attributes. The resulting array of must attributes will contain
|
||||
* unique members.
|
||||
*
|
||||
* @param array $attr An array of attribute names (strings) to add.
|
||||
*/
|
||||
private function addMustAttrs(array $attr): void
|
||||
{
|
||||
if (! is_array($attr) || ! count($attr))
|
||||
return;
|
||||
|
||||
$this->must_attrs = $this->must_attrs->merge($attr)->unique();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
* @deprecated use $this->may_force
|
||||
*/
|
||||
public function getForceMayAttrs(): Collection
|
||||
{
|
||||
return $this->may_force;
|
||||
if (! $this->child_classes->contains($name))
|
||||
$this->child_classes->push($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,42 +69,26 @@ final class ObjectClass extends Base
|
||||
*/
|
||||
public function getMayAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
// If we dont need our parents, then we'll just return ours.
|
||||
if (! $parents)
|
||||
return $this->may_attrs
|
||||
->sortBy(fn($item)=>strtolower($item->name.$item->source));
|
||||
$attrs = $this->attributes
|
||||
->filter(fn($item)=>! $item->is_must)
|
||||
->transform(function($item) {
|
||||
$item->source = $this->name;
|
||||
return $item;
|
||||
});
|
||||
|
||||
$attrs = $this->may_attrs;
|
||||
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class->getMayAttrs($parents));
|
||||
|
||||
// Remove any duplicates
|
||||
$attrs = $attrs->unique(function($item) { return $item->name; });
|
||||
if ($parents)
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class
|
||||
->getMayAttrs($parents)
|
||||
->transform(function($item) use ($object_class) {
|
||||
$item->source = $item->source ?: $object_class->name;
|
||||
return $item;
|
||||
}));
|
||||
|
||||
// Return a sorted list
|
||||
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
|
||||
* This differs from getMayAttrs in that it returns an array of strings rather than
|
||||
* array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents An array of ObjectClass objects to use when traversing
|
||||
* the inheritance tree. This presents some what of a bootstrapping problem
|
||||
* as we must fetch all objectClasses to determine through inheritance which
|
||||
* attributes this objectClass provides.
|
||||
* @return Collection The array of allowed attribute names (strings).
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrs
|
||||
* @see getMayAttrs
|
||||
* @see getMustAttrNames
|
||||
*/
|
||||
public function getMayAttrNames(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMayAttrs($parents)->ppluck('name');
|
||||
return $attrs
|
||||
->unique(fn($item)=>$item->name)
|
||||
->sortBy(fn($item)=>$item->name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -365,41 +105,26 @@ final class ObjectClass extends Base
|
||||
*/
|
||||
public function getMustAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
// If we dont need our parents, then we'll just return ours.
|
||||
if (! $parents)
|
||||
return $this->must_attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
$attrs = $this->attributes
|
||||
->filter(fn($item)=>$item->is_must)
|
||||
->transform(function($item) {
|
||||
$item->source = $this->name;
|
||||
return $item;
|
||||
});
|
||||
|
||||
$attrs = $this->must_attrs;
|
||||
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class->getMustAttrs($parents));
|
||||
|
||||
// Remove any duplicates
|
||||
$attrs = $attrs->unique(function($item) { return $item->name; });
|
||||
if ($parents)
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class
|
||||
->getMustAttrs($parents)
|
||||
->transform(function($item) use ($object_class) {
|
||||
$item->source = $item->source ?: $object_class->name;
|
||||
return $item;
|
||||
}));
|
||||
|
||||
// Return a sorted list
|
||||
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
|
||||
* This differs from getMustAttrs in that it returns an array of strings rather than
|
||||
* array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents An array of ObjectClass objects to use when traversing
|
||||
* the inheritance tree. This presents some what of a bootstrapping problem
|
||||
* as we must fetch all objectClasses to determine through inheritance which
|
||||
* attributes this objectClass provides.
|
||||
* @return Collection The array of allowed attribute names (strings).
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrs
|
||||
* @see getMayAttrs
|
||||
* @see getMayAttrNames
|
||||
*/
|
||||
public function getMustAttrNames(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMustAttrs($parents)->ppluck('name');
|
||||
return $attrs
|
||||
->unique(fn($item)=>$item->name)
|
||||
->sortBy(fn($item)=>$item->name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -426,27 +151,6 @@ final class ObjectClass extends Base
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the objectClass names from which this objectClass inherits.
|
||||
*
|
||||
* @return Collection An array of objectClass names (strings)
|
||||
* @deprecated use $this->sup_classes;
|
||||
*/
|
||||
public function getSupClasses(): Collection
|
||||
{
|
||||
return $this->sup_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of this objectClass: STRUCTURAL, ABSTRACT, or AUXILIARY.
|
||||
*
|
||||
* @deprecated use $this->type_name
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this objectclass is auxiliary
|
||||
*
|
||||
@ -457,39 +161,109 @@ final class ObjectClass extends Base
|
||||
return $this->type === Server::OC_AUXILIARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an array is listed in the may_force attrs
|
||||
*/
|
||||
public function isForceMay(string $attr): bool
|
||||
{
|
||||
return $this->may_force->ppluck('name')->contains($attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this objectClass is related to $oclass
|
||||
*
|
||||
* @param array $oclass ObjectClasses that this attribute may be related to
|
||||
* @return bool
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function isRelated(array $oclass): bool
|
||||
{
|
||||
// If I am in the array, we'll just return false
|
||||
if (in_array_ignore_case($this->name,$oclass))
|
||||
return FALSE;
|
||||
|
||||
foreach ($oclass as $object_class)
|
||||
if ($object_class->isStructural() && in_array_ignore_case($this->name,$object_class->getParents()->pluck('name')))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function isStructural(): bool
|
||||
{
|
||||
return $this->type === Server::OC_STRUCTURAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ObjectClass object given a raw LDAP objectClass string.
|
||||
*
|
||||
* eg: ( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )
|
||||
*
|
||||
* @param string $line Schema Line
|
||||
*/
|
||||
protected function parse(string $line): void
|
||||
{
|
||||
Log::debug(sprintf('%s:Parsing ObjectClass [%s]',self::LOGKEY,$line));
|
||||
|
||||
// Init
|
||||
$this->attributes = collect();
|
||||
$this->sup_classes = collect();
|
||||
$this->child_classes = collect();
|
||||
|
||||
parent::parse($line);
|
||||
}
|
||||
|
||||
protected function parse_chunk(array $strings,int &$i): void
|
||||
{
|
||||
switch ($strings[$i]) {
|
||||
case 'SUP':
|
||||
if ($strings[$i+1] !== '(') {
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[++$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
if ($strings[$i] !== '$')
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[$i]));
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i+1]));
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case SUP returned (%s)',self::LOGKEY,$this->sup_classes->join(',')));
|
||||
break;
|
||||
|
||||
case 'ABSTRACT':
|
||||
$this->type = Server::OC_ABSTRACT;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case ABSTRACT returned (%s)',self::LOGKEY,$this->type));
|
||||
break;
|
||||
|
||||
case 'STRUCTURAL':
|
||||
$this->type = Server::OC_STRUCTURAL;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case STRUCTURAL returned (%s)',self::LOGKEY,$this->type));
|
||||
break;
|
||||
|
||||
case 'AUXILIARY':
|
||||
$this->type = Server::OC_AUXILIARY;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case AUXILIARY returned (%s)',self::LOGKEY,$this->type));
|
||||
break;
|
||||
|
||||
case 'MUST':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
foreach ($attrs as $string) {
|
||||
$attr = clone config('server')->schema('attributetypes',$string);
|
||||
|
||||
if (! $attr->forced_as_may)
|
||||
$attr->setMust();
|
||||
|
||||
$this->attributes->push($attr);
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case MUST returned (%s) (%s)',self::LOGKEY,$attrs->join(','),$this->forced_as_may ? 'FORCED MAY' : 'MUST'));
|
||||
break;
|
||||
|
||||
case 'MAY':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
foreach ($attrs as $string)
|
||||
$this->attributes->push(config('server')->schema('attributetypes',$string));
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('%s:- Case MAY returned (%s)',self::LOGKEY,$attrs->join(',')));
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::parse_chunk($strings,$i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an LDAP schema list
|
||||
*
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
/**
|
||||
* A simple class for representing AttributeTypes used only by the ObjectClass class.
|
||||
*
|
||||
* Users should never instantiate this class. It represents an attribute internal to
|
||||
* an ObjectClass. If PHP supported inner-classes and variable permissions, this would
|
||||
* be interior to class ObjectClass and flagged private. The reason this class is used
|
||||
* and not the "real" class AttributeType is because this class supports the notion of
|
||||
* a "source" objectClass, meaning that it keeps track of which objectClass originally
|
||||
* specified it. This class is therefore used by the class ObjectClass to determine
|
||||
* inheritance.
|
||||
*/
|
||||
final class ObjectClassAttribute extends Base {
|
||||
// This Attribute's root.
|
||||
private string $source;
|
||||
public bool $required = FALSE;
|
||||
|
||||
/**
|
||||
* Creates a new ObjectClassAttribute with specified name and source objectClass.
|
||||
*
|
||||
* @param string $name the name of the new attribute.
|
||||
* @param string $source the name of the ObjectClass which specifies this attribute.
|
||||
*/
|
||||
public function __construct($name,$source)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'source' => $this->source,
|
||||
default => parent::__get($key),
|
||||
};
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ use LdapRecord\Query\Builder;
|
||||
use LdapRecord\Query\Collection as LDAPCollection;
|
||||
use LdapRecord\Query\ObjectNotFoundException;
|
||||
|
||||
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass};
|
||||
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,ObjectClass};
|
||||
use App\Exceptions\InvalidUsage;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
@ -28,9 +28,10 @@ final class Server
|
||||
private Collection $attributetypes;
|
||||
private Collection $ldapsyntaxes;
|
||||
private Collection $matchingrules;
|
||||
private Collection $matchingruleuse;
|
||||
private Collection $objectclasses;
|
||||
|
||||
private Model $rootDSE;
|
||||
|
||||
/* ObjectClass Types */
|
||||
public const OC_STRUCTURAL = 0x01;
|
||||
public const OC_ABSTRACT = 0x02;
|
||||
@ -38,6 +39,8 @@ final class Server
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->rootDSE = self::rootDSE();
|
||||
|
||||
$this->attributetypes = collect();
|
||||
$this->ldapsyntaxes = collect();
|
||||
$this->matchingrules = collect();
|
||||
@ -47,10 +50,6 @@ final class Server
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match($key) {
|
||||
'attributetypes' => $this->attributetypes,
|
||||
'ldapsyntaxes' => $this->ldapsyntaxes,
|
||||
'matchingrules' => $this->matchingrules,
|
||||
'objectclasses' => $this->objectclasses,
|
||||
'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),
|
||||
@ -67,10 +66,10 @@ final class Server
|
||||
* @return Collection
|
||||
* @testedin GetBaseDNTest::testBaseDNExists();
|
||||
*/
|
||||
public static function baseDNs(bool $objects=FALSE): Collection
|
||||
public static function baseDNs(bool $objects=TRUE): Collection
|
||||
{
|
||||
try {
|
||||
$rootdse = self::rootDSE();
|
||||
$namingcontexts = collect(config('pla.base_dns') ?: self::rootDSE()?->namingcontexts);
|
||||
|
||||
/**
|
||||
* LDAP Error Codes:
|
||||
@ -176,16 +175,16 @@ final class Server
|
||||
}
|
||||
|
||||
if (! $objects)
|
||||
return collect($rootdse->namingcontexts);
|
||||
return $namingcontexts;
|
||||
|
||||
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($rootdse) {
|
||||
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($namingcontexts) {
|
||||
$result = collect();
|
||||
|
||||
// @note: Incase our rootDSE didnt return a namingcontext, we'll have no base DNs
|
||||
foreach (($rootdse->namingcontexts ?: []) as $dn)
|
||||
foreach ($namingcontexts as $dn)
|
||||
$result->push(self::get($dn)->read()->find($dn));
|
||||
|
||||
return $result->filter();
|
||||
return $result->filter()->sort(fn($item)=>$item->sort_key);
|
||||
});
|
||||
}
|
||||
|
||||
@ -257,18 +256,6 @@ final class Server
|
||||
return $rootdse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Schema DN
|
||||
*
|
||||
* @return string
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public static function schemaDN(): string
|
||||
{
|
||||
return collect(self::rootDSE()->subschemasubentry)
|
||||
->first();
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
@ -288,7 +275,6 @@ final class Server
|
||||
'c' // Needed for the tree to show icons for countries
|
||||
]))
|
||||
->list()
|
||||
->orderBy('dn')
|
||||
->get() ?: NULL;
|
||||
}
|
||||
|
||||
@ -307,15 +293,37 @@ final class Server
|
||||
}
|
||||
|
||||
/**
|
||||
* This function determines if the specified attribute is contained in the force_may list
|
||||
* as configured in config.php.
|
||||
* Get an attribute key for an attributetype name
|
||||
*
|
||||
* @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
|
||||
* @param string $key
|
||||
* @return int|bool
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function isForceMay($attr_name): bool
|
||||
public function get_attr_id(string $key): int|bool
|
||||
{
|
||||
return in_array($attr_name,config('pla.force_may',[]));
|
||||
static $attributes = $this->schema('attributetypes');
|
||||
|
||||
$attrid = $attributes->search(fn($item)=>$item->names->contains($key));
|
||||
|
||||
// Second chance search using lowercase items (our Entry attribute keys are lowercase)
|
||||
if ($attrid === FALSE)
|
||||
$attrid = $attributes->search(fn($item)=>$item->names_lc->contains(strtolower($key)));
|
||||
|
||||
return $attrid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an OID, return the ldapsyntax for the OID
|
||||
*
|
||||
* @param string $oid
|
||||
* @return LDAPSyntax|null
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function get_syntax(string $oid): ?LDAPSyntax
|
||||
{
|
||||
return (($id=$this->schema('ldapsyntaxes')->search(fn($item)=>$item->oid === $oid)) !== FALSE)
|
||||
? $this->ldapsyntaxes[$id]
|
||||
: NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -336,212 +344,167 @@ final class Server
|
||||
* @param string $item Schema Item to Fetch
|
||||
* @param string|null $key
|
||||
* @return Collection|LDAPSyntax|Base|NULL
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function schema(string $item,?string $key=NULL): Collection|LDAPSyntax|Base|NULL
|
||||
{
|
||||
// Ensure our item to fetch is lower case
|
||||
$item = strtolower($item);
|
||||
if ($key)
|
||||
$key = strtolower($key);
|
||||
|
||||
$result = Cache::remember('schema'.$item,config('ldap.cache.time'),function() use ($item) {
|
||||
// First pass if we have already retrieved the schema item
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
case 'ldapsyntaxes':
|
||||
case 'matchingrules':
|
||||
case 'objectclasses':
|
||||
if ($this->{$item}->count())
|
||||
return $this->{$item};
|
||||
if (! $this->{$item}->count()) {
|
||||
$this->{$item} = Cache::remember('schema.'.$item,config('ldap.cache.time'),function() use ($item) {
|
||||
// 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']);
|
||||
|
||||
break;
|
||||
// If our schema's null, we didnt find it.
|
||||
if (! $schema)
|
||||
throw new Exception('Couldnt find schema at:'.$schema_dn);
|
||||
|
||||
// This error message is not localized as only developers should ever see it
|
||||
default:
|
||||
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
|
||||
}
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
|
||||
// build the array of attribueTypes
|
||||
//$syntaxes = $this->SchemaSyntaxes($dn);
|
||||
|
||||
// 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']);
|
||||
|
||||
// If our schema's null, we didnt find it.
|
||||
if (! $schema)
|
||||
throw new Exception('Couldnt find schema at:'.$schema_dn);
|
||||
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
|
||||
// build the array of attribueTypes
|
||||
//$syntaxes = $this->SchemaSyntaxes($dn);
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new AttributeType($line);
|
||||
$this->attributetypes->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
// go back and add data from aliased attributeTypes
|
||||
foreach ($this->attributetypes as $o) {
|
||||
/* foreach of the attribute's aliases, create a new entry in the attrs array
|
||||
* 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(',')));
|
||||
|
||||
foreach ($o->aliases as $alias) {
|
||||
$new_attr = clone $o;
|
||||
$new_attr->setName($alias);
|
||||
$new_attr->addAlias($o->name);
|
||||
$new_attr->removeAlias($alias);
|
||||
|
||||
$this->attributetypes->put(strtolower($alias),$new_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now go through and reference the parent/child relationships
|
||||
foreach ($this->attributetypes as $o)
|
||||
if ($o->sup_attribute) {
|
||||
$parent = strtolower($o->sup_attribute);
|
||||
|
||||
if ($this->attributetypes->has($parent) !== FALSE)
|
||||
$this->attributetypes[$parent]->addChild($o->name);
|
||||
}
|
||||
|
||||
// go through any children and add details if the child doesnt have them (ie, cn inherits name)
|
||||
// @todo This doesnt traverse children properly, so children of children may not get the settings they should
|
||||
foreach ($this->attributetypes as $parent) {
|
||||
foreach ($parent->children as $child) {
|
||||
$child = strtolower($child);
|
||||
|
||||
/* only overwrite the child's SINGLE-VALUE property if the parent has it set, and the child doesnt
|
||||
* (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
|
||||
if (! is_null($parent->is_single_value) && is_null($this->attributetypes[$child]->is_single_value))
|
||||
$this->attributetypes[$child]->setIsSingleValue($parent->is_single_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the used in and required_by values.
|
||||
foreach ($this->schema('objectclasses') as $object_class) {
|
||||
$must_attrs = $object_class->getMustAttrNames();
|
||||
$may_attrs = $object_class->getMayAttrNames();
|
||||
$oclass_attrs = $must_attrs->merge($may_attrs)->unique();
|
||||
|
||||
// Add Used In.
|
||||
foreach ($oclass_attrs as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name)))
|
||||
$this->attributetypes[strtolower($attr_name)]->addUsedInObjectClass($object_class->name,$object_class->isStructural());
|
||||
|
||||
// Add Required By.
|
||||
foreach ($must_attrs as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name)))
|
||||
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name,$object_class->isStructural());
|
||||
|
||||
// Force May
|
||||
foreach ($object_class->getForceMayAttrs() as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name->name)))
|
||||
$this->attributetypes[strtolower($attr_name->name)]->setForceMay();
|
||||
}
|
||||
|
||||
return $this->attributetypes;
|
||||
|
||||
case 'ldapsyntaxes':
|
||||
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new LDAPSyntax($line);
|
||||
$this->ldapsyntaxes->put(strtolower($o->oid),$o);
|
||||
}
|
||||
|
||||
return $this->ldapsyntaxes;
|
||||
|
||||
case 'matchingrules':
|
||||
Log::debug(sprintf('%s:Matching Rules',self::LOGKEY));
|
||||
$this->matchingruleuse = collect();
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new MatchingRule($line);
|
||||
$this->matchingrules->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
/*
|
||||
* For each MatchingRuleUse entry, add the attributes who use it to the
|
||||
* MatchingRule in the $rules array.
|
||||
*/
|
||||
if ($schema->matchingruleuse) {
|
||||
foreach ($schema->matchingruleuse as $line) {
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new MatchingRuleUse($line);
|
||||
$this->matchingruleuse->put($o->name_lc,$o);
|
||||
|
||||
if ($this->matchingrules->has($o->name_lc) !== FALSE)
|
||||
$this->matchingrules[$o->name_lc]->setUsedByAttrs($o->getUsedByAttrs());
|
||||
$o = new AttributeType($line);
|
||||
$this->attributetypes->push($o);
|
||||
}
|
||||
|
||||
foreach ($this->attributetypes as $o) {
|
||||
// Now go through and reference the parent/child relationships
|
||||
if ($o->sup_attribute) {
|
||||
$attrid = $this->get_attr_id($o->sup_attribute);
|
||||
|
||||
if (! $this->attributetypes[$attrid]->children->contains($o->oid))
|
||||
$this->attributetypes[$attrid]->addChild($o->oid);
|
||||
}
|
||||
|
||||
// go through any children and add details if the child doesnt have them (ie, cn inherits name)
|
||||
foreach ($o->children as $child) {
|
||||
$attrid = $this->attributetypes->search(fn($o)=>$o->oid === $child);
|
||||
|
||||
/* only overwrite the child's SINGLE-VALUE property if the parent has it set, and the child doesnt
|
||||
* (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
|
||||
if (! is_null($o->is_single_value) && is_null($this->attributetypes[$attrid]->is_single_value))
|
||||
$this->attributetypes[$attrid]->setIsSingleValue($o->is_single_value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->attributetypes;
|
||||
|
||||
case 'ldapsyntaxes':
|
||||
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new LDAPSyntax($line);
|
||||
$this->ldapsyntaxes->push($o);
|
||||
}
|
||||
|
||||
return $this->ldapsyntaxes;
|
||||
|
||||
case 'matchingrules':
|
||||
Log::debug(sprintf('%s:Matching Rules',self::LOGKEY));
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new MatchingRule($line);
|
||||
$this->matchingrules->push($o);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* No MatchingRuleUse entry in the subschema, so brute-forcing
|
||||
* the reverse-map for the "$rule->getUsedByAttrs()" data.*/
|
||||
foreach ($this->schema('attributetypes') as $attr) {
|
||||
$rule_key = strtolower($attr->getEquality());
|
||||
$rule_id = $this->matchingrules->search(fn($item)=>$item->oid === $attr->equality);
|
||||
|
||||
if ($this->matchingrules->has($rule_key) !== FALSE)
|
||||
$this->matchingrules[$rule_key]->addUsedByAttr($attr->name);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->matchingrules;
|
||||
|
||||
case 'objectclasses':
|
||||
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new ObjectClass($line,$this);
|
||||
$this->objectclasses->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
// Now go through and reference the parent/child relationships
|
||||
foreach ($this->objectclasses as $o)
|
||||
foreach ($o->getSupClasses() as $parent) {
|
||||
$parent = strtolower($parent);
|
||||
|
||||
if (! $this->objectclasses->contains($parent))
|
||||
$this->objectclasses[$parent]->addChildObjectClass($o->name);
|
||||
if ($rule_id !== FALSE)
|
||||
$this->matchingrules[$rule_id]->addUsedByAttr($attr->name);
|
||||
}
|
||||
|
||||
return $this->objectclasses;
|
||||
return $this->matchingrules;
|
||||
|
||||
// Shouldnt get here
|
||||
default:
|
||||
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
|
||||
}
|
||||
});
|
||||
case 'objectclasses':
|
||||
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
|
||||
|
||||
return is_null($key) ? $result : $result->get($key);
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new ObjectClass($line);
|
||||
$this->objectclasses->push($o);
|
||||
}
|
||||
|
||||
foreach ($this->objectclasses as $o) {
|
||||
// Now go through and reference the parent/child relationships
|
||||
foreach ($o->sup_classes as $sup) {
|
||||
$oc_id = $this->objectclasses->search(fn($item)=>$item->name === $sup);
|
||||
|
||||
if (($oc_id !== FALSE) && (! $this->objectclasses[$oc_id]->child_classes->contains($o->name)))
|
||||
$this->objectclasses[$oc_id]->addChildObjectClass($o->name);
|
||||
}
|
||||
|
||||
// Add the used in and required_by values for attributes.
|
||||
foreach ($o->attributes as $attribute) {
|
||||
if (($attrid = $this->schema('attributetypes')->search(fn($item)=>$item->oid === $attribute->oid)) !== FALSE) {
|
||||
// Add Used In.
|
||||
$this->attributetypes[$attrid]->addUsedInObjectClass($o->oid,$o->isStructural());
|
||||
|
||||
// Add Required By.
|
||||
if ($attribute->is_must)
|
||||
$this->attributetypes[$attrid]->addRequiredByObjectClass($o->oid,$o->isStructural());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put the updated attributetypes back in the cache
|
||||
Cache::put('schema.attributetypes',$this->attributetypes,config('ldap.cache.time'));
|
||||
|
||||
return $this->objectclasses;
|
||||
|
||||
// Shouldnt get here
|
||||
default:
|
||||
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (is_null($key))
|
||||
return $this->{$item};
|
||||
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
$attrid = $this->get_attr_id($key);
|
||||
|
||||
$attr = ($attrid === FALSE)
|
||||
? new AttributeType($key)
|
||||
: clone $this->{$item}->get($attrid);
|
||||
|
||||
$attr->setName($attr->names->get($attr->names_lc->search(strtolower($key))) ?: $key);
|
||||
|
||||
return $attr;
|
||||
|
||||
default:
|
||||
return $this->{$item}->get($key)
|
||||
?: $this->{$item}->first(fn($item)=>$item->name_lc === strtolower($key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an OID, return the ldapsyntax for the OID
|
||||
* Get the Schema DN
|
||||
*
|
||||
* @param string $oid
|
||||
* @return LDAPSyntax|null
|
||||
* @return string
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function schemaSyntaxName(string $oid): ?LDAPSyntax
|
||||
public function schemaDN(): string
|
||||
{
|
||||
return $this->schema('ldapsyntaxes',$oid);
|
||||
return Arr::get($this->rootDSE->subschemasubentry,0);
|
||||
}
|
||||
}
|
450
app/Classes/Template.php
Normal file
450
app/Classes/Template.php
Normal file
@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Ldap\Entry;
|
||||
|
||||
class Template
|
||||
{
|
||||
private const LOGKEY = 'T--';
|
||||
private const LOCK_TIME = 600;
|
||||
|
||||
private(set) string $file;
|
||||
private Collection $template;
|
||||
private(set) bool $invalid = FALSE;
|
||||
private(set) string $reason = '';
|
||||
private Collection $on_change_target;
|
||||
private Collection $on_change_attribute;
|
||||
private bool $on_change_processed = FALSE;
|
||||
|
||||
public function __construct(string $file)
|
||||
{
|
||||
$td = Storage::disk(config('pla.template.dir'));
|
||||
$this->on_change_attribute = collect();
|
||||
$this->on_change_target = collect();
|
||||
|
||||
$this->file = $file;
|
||||
|
||||
try {
|
||||
// @todo Load in the proper attribute objects and objectclass objects
|
||||
// @todo Make sure we have a structural objectclass, or make the template invalid
|
||||
$this->template = collect(json_decode($td->get($file),null,512,JSON_OBJECT_AS_ARRAY|JSON_THROW_ON_ERROR));
|
||||
|
||||
} catch (\JsonException $e) {
|
||||
$this->invalid = TRUE;
|
||||
$this->reason = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'attributes','objectclasses' => collect($this->template->get($key)),
|
||||
'enabled' => $this->template->get($key,FALSE) && (! $this->invalid),
|
||||
'icon','regexp','title' => $this->template->get($key),
|
||||
'name' => Str::replaceEnd('.json','',$this->file),
|
||||
'order' => $this->attributes->map(fn($item)=>Arr::get($item,'order')),
|
||||
|
||||
default => throw new \Exception('Unknown key: '.$key),
|
||||
};
|
||||
}
|
||||
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return $this->template->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for an attribute
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function attribute(string $attribute): Collection|NULL
|
||||
{
|
||||
$key = $this->attributes->search(fn($item,$key)=>! strcasecmp($key,$attribute));
|
||||
return collect($this->attributes->get($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an template attributes select options
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return Collection|NULL
|
||||
*/
|
||||
public function attributeOptions(string $attribute): Collection|NULL
|
||||
{
|
||||
return ($x=$this->attribute($attribute)?->get('options'))
|
||||
? collect($x)->map(fn($item,$key)=>['id'=>$key,'value'=>$item])
|
||||
: NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the attribute has been marked as read-only
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function attributeReadOnly(string $attribute): bool
|
||||
{
|
||||
return ($x=$this->attribute($attribute)?->get('readonly')) && $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title we should use for an attribute
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function attributeTitle(string $attribute): string|NULL
|
||||
{
|
||||
return $this->attribute($attribute)?->get('display');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title we should use for an attribute
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function attributeType(string $attribute): string|NULL
|
||||
{
|
||||
return $this->attribute($attribute)?->get('type');
|
||||
}
|
||||
|
||||
public function attributeValue(string $attribute): string|NULL
|
||||
{
|
||||
if ($x=$this->attribute($attribute)->get('value')) {
|
||||
list($command,$args) = preg_split('/^=([a-zA-Z]+)\((.+)\)$/',$x,-1,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
return match ($command) {
|
||||
'getNextNumber' => $this->getNextNumber($args),
|
||||
default => NULL,
|
||||
};
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next number for an attribute
|
||||
*
|
||||
* As part of getting the next number, we'll use a lock to avoid any potential clashes. The lock is obtained by
|
||||
* two lock files:
|
||||
* a: Read a session lock (our session id), use that number if it exists, otherwise,
|
||||
* b: Query the ldap server for the attribute, sort by number
|
||||
* c: Read a system lock, if it exists, and use that as our start base (otherwise use a config() base)
|
||||
* d: Starting at base, find the next free number
|
||||
* e: When number identified, put it in the system lock with our session id
|
||||
* f: Put the number in our session lock, with a timeout
|
||||
* g: Read the system lock, make sure our session id is still in it, if not, go to (d) with our number as the base
|
||||
* h: Remove our session id from the system lock (our number is unique)
|
||||
*
|
||||
* When using the number to create an entry:
|
||||
* + Read our session lock, confirm the number is still in it, if not fail validation and bounce back
|
||||
* + Create the entry
|
||||
* + Delete our session lock
|
||||
*
|
||||
* @param string $arg
|
||||
* @return int|NULL
|
||||
*/
|
||||
private function getNextNumber(string $arg): int|NULL
|
||||
{
|
||||
if (! preg_match('/;/',$arg)) {
|
||||
Log::alert(sprintf('%s:Invalid argument given to getNextNumber [%s]',self::LOGKEY,$arg));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
list($start,$attr) = preg_split('(([^,]+);(\w+))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$attr = strtolower($attr);
|
||||
|
||||
// If we recently got a number, return it
|
||||
if ($number=Cache::get($attr.':'.Session::id()))
|
||||
return $number;
|
||||
|
||||
$cache = Cache::get($attr.':system');
|
||||
Log::debug(sprintf('%s:System Cache has',self::LOGKEY),['cache'=>$cache]);
|
||||
|
||||
if (! Arr::get($cache,'number'))
|
||||
$number = config('pla.template.getnextnumber.'.$attr,0);
|
||||
else
|
||||
$number = Arr::get($cache,'number')+1;
|
||||
|
||||
Log::debug(sprintf('%s:Starting with [%d] for [%s]',self::LOGKEY,$number,$attr));
|
||||
|
||||
$o = config('server');
|
||||
$bases = ($start === '/') ? $o->baseDNs() : collect($start);
|
||||
|
||||
$result = collect();
|
||||
$complete = [];
|
||||
|
||||
do {
|
||||
$sizelimit = FALSE;
|
||||
|
||||
// Get the current numbers
|
||||
foreach ($bases as $base) {
|
||||
if (Arr::get($complete,$dn=$base->getDN()))
|
||||
continue;
|
||||
|
||||
$query = Entry::query()
|
||||
->setDN($base)
|
||||
->select([$attr])
|
||||
->where($attr,'*')
|
||||
->notFilter(fn($q)=>$q->where($attr,'<=',$number-1));
|
||||
|
||||
if ($result->count())
|
||||
$query->notFilter(fn($q)=>$q->where($attr,'>=',$result->min()));
|
||||
|
||||
$result = $result->merge(($x=$query
|
||||
->search()
|
||||
->orderBy($attr)
|
||||
->get())
|
||||
->pluck($attr)
|
||||
->flatten());
|
||||
|
||||
// If we hit a sizelimit on this run
|
||||
$base_sizelimit = $query->getModel()->hasMore();
|
||||
Log::debug(sprintf('%s:Query in [%s] returned [%d] entries and has more [%s]',self::LOGKEY,$base,$x->count(),$base_sizelimit ? 'TRUE' : 'FALSE'));
|
||||
|
||||
if (! $base_sizelimit)
|
||||
$complete[$dn] = TRUE;
|
||||
else
|
||||
Log::info(sprintf('%s:Size Limit alert for [%s]',self::LOGKEY,$dn));
|
||||
|
||||
$sizelimit = $sizelimit || $base_sizelimit;
|
||||
}
|
||||
|
||||
$result = $result
|
||||
->sort()
|
||||
->unique();
|
||||
|
||||
Log::debug(sprintf('%s:Result has [%s]',self::LOGKEY,$result->join('|')));
|
||||
|
||||
if ($result->count())
|
||||
foreach ($result as $item) {
|
||||
Log::debug(sprintf('%s:Checking [%d] against [%s]',self::LOGKEY,$number,$item));
|
||||
if ($number < $item)
|
||||
break;
|
||||
|
||||
$number += 1;
|
||||
}
|
||||
|
||||
else
|
||||
$number += 1;
|
||||
|
||||
// Remove redundant entries
|
||||
$result = $result->filter(fn($item)=>$item>$number);
|
||||
|
||||
if ($sizelimit)
|
||||
Log::debug(sprintf('%s:We got a sizelimit.',self::LOGKEY),['number'=>$number,'result_min'=>$result->min(),'result_count'=>$result->count()]);
|
||||
|
||||
/*
|
||||
* @todo This might need some additional work:
|
||||
* EG: if sizelimit is 5
|
||||
* if result has 1,2,3,4,20 [size limit]
|
||||
* we re-enquire (4=>20) and get 7,8,9,10,11 [size limit]
|
||||
* we re-enquire (4=>7) and get 5,6 [no size limit]
|
||||
* we calculate 12, and accept it because no size limit, but we didnt test for 12
|
||||
*/
|
||||
} while ($sizelimit);
|
||||
|
||||
// We found our number
|
||||
Log::debug(sprintf('%s:Autovalue for Attribute [%s] in Session [%s] Storing [%d]',self::LOGKEY,$attr,Session::id(),$number));
|
||||
Cache::put($attr.':system',['number'=>$number,'session'=>Session::id(),self::LOCK_TIME*2]);
|
||||
Cache::put($attr.':'.Session::id(),$number,self::LOCK_TIME);
|
||||
sleep(1);
|
||||
|
||||
// If the session still has our session ID, then our number is ours
|
||||
return (Arr::get(Cache::get($attr.':system'),'session') === Session::id())
|
||||
? $number
|
||||
: NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the onChange JavaScript for an attribute
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return Collection|NULL
|
||||
*/
|
||||
public function onChange(string $attribute): Collection|NULL
|
||||
{
|
||||
if (! $this->on_change_processed)
|
||||
$this->onChangeProcessing();
|
||||
|
||||
return $this->on_change_attribute
|
||||
->get(strtolower($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this attribute's value populated by any onChange processing rules
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function onChangeAttribute(string $attribute): bool
|
||||
{
|
||||
if (! $this->on_change_processed)
|
||||
$this->onChangeProcessing();
|
||||
|
||||
return $this->on_change_attribute
|
||||
->has(strtolower($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the attributes for onChange JavaScript
|
||||
*/
|
||||
/**
|
||||
* Return the onchange JavaScript for attribute
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function onChangeProcessing(): void
|
||||
{
|
||||
foreach (Arr::get($this->template,'attributes',[]) as $attribute => $detail) {
|
||||
$result = collect();
|
||||
|
||||
foreach (Arr::get($detail,'onchange',[]) as $item) {
|
||||
list($command,$args) = preg_split('/^=([a-zA-Z]+)\((.+)\)$/',$item,-1,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
switch ($command) {
|
||||
case 'autoFill':
|
||||
$result->push($this->autofill($args));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($result->count())
|
||||
$this->on_change_attribute->put(strtolower($attribute),$result);
|
||||
}
|
||||
|
||||
$this->on_change_processed = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this attribute's value populated by any onChange processing rules
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function onChangeTarget(string $attribute): bool
|
||||
{
|
||||
if (! $this->on_change_processed)
|
||||
$this->onChangeProcessing();
|
||||
|
||||
return $this->on_change_target
|
||||
->has(strtolower($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* autoFill - javascript to have one attribute fill the value of another
|
||||
*
|
||||
* args: is a literal string, with two parts, delimited by a semi-colon ;
|
||||
* + The first part is the attribute that will be populated
|
||||
* + The second part may contain many fields like %attr|start-end/flags|additionalcontrolchar%
|
||||
* to substitute values read from other fields.
|
||||
* + |start-end is optional, but must be present if the k flag is used
|
||||
* + /flags is optional
|
||||
* + |additionalcontrolchar is optional, and specific to a flag being used
|
||||
*
|
||||
* + flags may be:
|
||||
* T:(?) Read display text from selection item (drop-down list), otherwise, read the value of the field
|
||||
* For fields that aren't selection items, /T shouldn't be used, and the field value will always be read
|
||||
* k:(?) Tokenize:
|
||||
* If the "k" flag is not given:
|
||||
* + A |start-end instruction will perform a sub-string operation upon the value of the attr, passing
|
||||
* character positions start-end through
|
||||
* + start can be 0 for first character, or any other integer
|
||||
* + end can be 0 for last character, or any other integer for a specific position
|
||||
* If the "k" flag is given:
|
||||
* + The string read will be split into fields, using : as a delimiter
|
||||
* + start indicates which field number to pass through
|
||||
*
|
||||
* If additionalcontrolchar is given, it will be used as delimiter (e.g. this allows for splitting
|
||||
* e-mail addresses into domain and domain-local part)
|
||||
* l: Make the result lower case
|
||||
* U: Make the result upper case
|
||||
* A:(?) Remap special characters to their corresponding ASCII value
|
||||
*
|
||||
* @note Attributes rendered on the page are lowercase, eg: <attribute id="gidnumber"> for gidNumber
|
||||
* @note JavaScript generated here depends on js/template.js
|
||||
* (?) = to test
|
||||
*/
|
||||
private function autofill(string $arg): string
|
||||
{
|
||||
if (! preg_match('/;/',$arg)) {
|
||||
Log::alert(sprintf('%s:Invalid argument given to autofill [%s]',self::LOGKEY,$arg));
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
// $attr has our attribute to update, $string is the format to use when updating it
|
||||
list($attr,$string) = preg_split('(([^,]+);(.*))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$this->on_change_target->put(strtolower($attr),$string);
|
||||
|
||||
$output = $string;
|
||||
//$result .= sprintf("\n// %s\n",$arg);
|
||||
|
||||
$m = [];
|
||||
// MATCH : 0 = highlevel match, 1 = attr, 2 = subst, 3 = mod, 4 = delimiter
|
||||
preg_match_all('/%(\w+)(?:\|(\d*-\d)+)?(?:\/([klTUA]+))?(?:\|(.)?)?%/U',$string,$m);
|
||||
|
||||
foreach ($m[0] as $index => $null) {
|
||||
$match_attr = strtolower($m[1][$index]);
|
||||
$match_subst = $m[2][$index];
|
||||
$match_mod = $m[3][$index];
|
||||
$match_delim = $m[4][$index];
|
||||
|
||||
$substrarray = [];
|
||||
|
||||
$result .= sprintf("var %s;\n",$match_attr);
|
||||
|
||||
if (str_contains($match_mod,'k')) {
|
||||
preg_match_all('/(\d+)/',trim($match_subst),$substrarray);
|
||||
|
||||
$delimiter = ($match_delim === '') ? ' ' : preg_quote($match_delim);
|
||||
$result .= sprintf(" %s = %s.split('%s')[%s];\n",$match_attr,$match_attr,$delimiter,$substrarray[1][0] ?? '0');
|
||||
|
||||
} else {
|
||||
// Work out the start and end chars needed from this value if we have a range specifier
|
||||
preg_match_all('/(\d*)-(\d+)/',$match_subst,$substrarray);
|
||||
if ((isset($substrarray[1][0]) && $substrarray[1][0]) || (isset($substrarray[2][0]) && $substrarray[2][0])) {
|
||||
$result .= sprintf("%s = get_attribute('%s',%d,%s);\n",
|
||||
$match_attr,$match_attr,
|
||||
$substrarray[1][0] ?? '0',
|
||||
$substrarray[2][0] ?: sprintf('%s.length',$match_attr));
|
||||
} else {
|
||||
$result .= sprintf("%s = get_attribute('%s');\n",$match_attr,$match_attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($match_mod,'l'))
|
||||
$result .= sprintf("%s = %s.toLowerCase();\n",$match_attr,$match_attr);
|
||||
|
||||
if (str_contains($match_mod,'U'))
|
||||
$result .= sprintf("%s = %s.toUpperCase();\n",$match_attr,$match_attr);
|
||||
|
||||
if (str_contains($match_mod,'A'))
|
||||
$result .= sprintf("%s = toAscii(%s);\n",$match_attr,$match_attr);
|
||||
|
||||
// For debugging
|
||||
//$result .= sprintf("console.log('%s will return:'+%s);\n",$match_attr,$match_attr);
|
||||
|
||||
// Reformat out output into JS variables
|
||||
$output = preg_replace('/'.preg_quote($m[0][$index],'/').'/','\'+'.$match_attr.'+\'',$output);
|
||||
}
|
||||
|
||||
$result .= sprintf("put_attribute('%s','%s');\n",strtolower($attr),$output);
|
||||
$result .= "\n";
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -12,26 +12,24 @@ use App\Classes\LDAP\Server;
|
||||
|
||||
class AjaxController extends Controller
|
||||
{
|
||||
private const LOGKEY = 'CAc';
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
return $base
|
||||
->transform(fn($item)=>
|
||||
[
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'lazy'=>TRUE,
|
||||
'icon'=>'fa-fw fas fa-sitemap',
|
||||
'tooltip'=>$item->getDn(),
|
||||
]);
|
||||
return Server::baseDNs()
|
||||
->map(fn($item)=> [
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'lazy'=>TRUE,
|
||||
'icon'=>'fa-fw fas fa-sitemap',
|
||||
'tooltip'=>$item->getDn(),
|
||||
])->values();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,13 +38,13 @@ class AjaxController extends Controller
|
||||
*/
|
||||
public function children(Request $request): Collection
|
||||
{
|
||||
$dn = Crypt::decryptString($request->query('key'));
|
||||
$dn = Crypt::decryptString($request->post('_key'));
|
||||
|
||||
// Sometimes our key has a command, so we'll ignore it
|
||||
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
|
||||
$dn = substr($dn,$x+1);
|
||||
|
||||
Log::debug(sprintf('%s: Query [%s]',__METHOD__,$dn));
|
||||
Log::debug(sprintf('%s:Query [%s]',self::LOGKEY,$dn));
|
||||
|
||||
return (config('server'))
|
||||
->children($dn)
|
||||
@ -59,13 +57,18 @@ class AjaxController extends Controller
|
||||
'tooltip'=>$item->getDn(),
|
||||
])
|
||||
->prepend(
|
||||
[
|
||||
'title'=>sprintf('[%s]',__('Create Entry')),
|
||||
'item'=>Crypt::encryptString(sprintf('*%s|%s','create',$dn)),
|
||||
'lazy'=>FALSE,
|
||||
'icon'=>'fas fa-fw fa-square-plus text-warning',
|
||||
'tooltip'=>__('Create new LDAP item here'),
|
||||
]);
|
||||
$request->create
|
||||
? [
|
||||
'title'=>sprintf('[%s]',__('Create Entry')),
|
||||
'item'=>Crypt::encryptString(sprintf('*%s|%s','create',$dn)),
|
||||
'lazy'=>FALSE,
|
||||
'icon'=>'fas fa-fw fa-square-plus text-warning',
|
||||
'tooltip'=>__('Create new LDAP item here'),
|
||||
]
|
||||
: []
|
||||
)
|
||||
->filter()
|
||||
->values();
|
||||
}
|
||||
|
||||
public function schema_view(Request $request)
|
||||
|
@ -7,8 +7,11 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LdapRecord\Auth\BindException;
|
||||
use LdapRecord\Container;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
@ -51,6 +54,45 @@ class LoginController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* When attempt to login
|
||||
*
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
* @throws \LdapRecord\ConnectionException
|
||||
* @throws \LdapRecord\ContainerException
|
||||
*/
|
||||
public function attemptLogin(Request $request)
|
||||
{
|
||||
$attempt = $this->guard()->attempt(
|
||||
$this->credentials($request), $request->boolean('remember')
|
||||
);
|
||||
|
||||
// If the login failed, and PLA is set to use DN login, check if the entry exists.
|
||||
// If the entry doesnt exist, it might be the root DN, which cannot be used to login
|
||||
if ((! $attempt) && $request->dn && config('pla.login.alert_rootdn',TRUE)) {
|
||||
// Double check our credentials, and see if they authenticate
|
||||
try {
|
||||
Container::getInstance()
|
||||
->getConnection()
|
||||
->auth()
|
||||
->bind($request->get(login_attr_name()),$request->get('password'));
|
||||
|
||||
} catch (BindException $e) {
|
||||
// Password incorrect, fail anyway
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$dn = config('server')->fetch($request->dn);
|
||||
$o = new Entry;
|
||||
|
||||
if (! $dn && $o->getConnection()->getLdapConnection()->errNo() === 32)
|
||||
abort(501,'Authentication succeeded, but the DN doesnt exist');
|
||||
}
|
||||
|
||||
return $attempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to delete our encrypted username/password cookies
|
||||
*
|
||||
|
@ -6,17 +6,18 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use LdapRecord\Exceptions\InsufficientAccessException;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use LdapRecord\Query\ObjectNotFoundException;
|
||||
use Nette\NotImplementedException;
|
||||
|
||||
use App\Classes\LDAP\Attribute\{Factory,Password};
|
||||
use App\Classes\LDAP\Server;
|
||||
use App\Classes\LDAP\Import\LDIF as LDIFImport;
|
||||
use App\Classes\LDAP\Export\LDIF as LDIFExport;
|
||||
use App\Exceptions\Import\{GeneralException,VersionException};
|
||||
@ -26,20 +27,9 @@ use App\Ldap\Entry;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
private function bases(): Collection
|
||||
{
|
||||
$base = Server::baseDNs(TRUE) ?: collect();
|
||||
private const LOGKEY = 'CHc';
|
||||
|
||||
return $base->transform(function($item) {
|
||||
return [
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'lazy'=>TRUE,
|
||||
'icon'=>'fa-fw fas fa-sitemap',
|
||||
'tooltip'=>$item->getDn(),
|
||||
];
|
||||
});
|
||||
}
|
||||
private const INTERNAL_POST = ['_auto_value','_key','_rdn','_rdn_new','_rdn_value','_step','_template','_token','_userpassword_hash'];
|
||||
|
||||
/**
|
||||
* Create a new object in the LDAP server
|
||||
@ -50,30 +40,45 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
|
||||
{
|
||||
if (! old('step',$request->validated('step')))
|
||||
if (! old('_step',$request->validated('_step')))
|
||||
abort(404);
|
||||
|
||||
$key = $this->request_key($request,collect(old()));
|
||||
|
||||
$template = NULL;
|
||||
$o = new Entry;
|
||||
$o->setRDNBase($key['dn']);
|
||||
|
||||
if (count($x=array_filter(old('objectclass',$request->objectclass)))) {
|
||||
$o->objectclass = $x;
|
||||
foreach (collect(old())->except(self::INTERNAL_POST) as $old => $value)
|
||||
$o->{$old} = array_filter($value);
|
||||
|
||||
if (old('_template',$request->validated('template'))) {
|
||||
$template = $o->template(old('_template',$request->validated('template')));
|
||||
|
||||
$o->objectclass = [Entry::TAG_NOTAG=>$template->objectclasses->toArray()];
|
||||
|
||||
foreach ($o->getAvailableAttributes()
|
||||
->filter(fn($item)=>$item->names_lc->intersect($template->attributes->keys()->map('strtolower'))->count())
|
||||
->sortBy(fn($item)=>Arr::get($template->order,$item->name)) as $ao)
|
||||
{
|
||||
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
|
||||
}
|
||||
|
||||
} elseif (count($x=collect(old('objectclass',$request->validated('objectclass')))->dot()->filter())) {
|
||||
$o->objectclass = Arr::undot($x);
|
||||
|
||||
// Also add in our required attributes
|
||||
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
|
||||
foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->is_must) as $ao)
|
||||
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
|
||||
|
||||
$o->setRDNBase($key['dn']);
|
||||
}
|
||||
|
||||
$step = $request->step ? $request->step+1 : old('step');
|
||||
$step = $request->get('_step') ? $request->get('_step')+1 : old('_step');
|
||||
|
||||
return view('frame')
|
||||
->with('subframe','create')
|
||||
->with('bases',$this->bases())
|
||||
->with('o',$o)
|
||||
->with('step',$step)
|
||||
->with('template',$template)
|
||||
->with('container',old('container',$key['dn']));
|
||||
}
|
||||
|
||||
@ -103,6 +108,7 @@ class HomeController extends Controller
|
||||
return $view
|
||||
->with('o',$o)
|
||||
->with('langtag',Entry::TAG_NOTAG)
|
||||
->with('template',NULL)
|
||||
->with('updated',FALSE);
|
||||
}
|
||||
|
||||
@ -110,14 +116,21 @@ class HomeController extends Controller
|
||||
{
|
||||
$key = $this->request_key($request,collect(old()));
|
||||
|
||||
$dn = sprintf('%s=%s,%s',$request->rdn,$request->rdn_value,$key['dn']);
|
||||
$dn = sprintf('%s=%s,%s',$request->get('_rdn'),$request->get('_rdn_value'),$key['dn']);
|
||||
|
||||
$o = new Entry;
|
||||
$o->setDn($dn);
|
||||
|
||||
foreach ($request->except(['_token','key','step','rdn','rdn_value','userpassword_hash']) as $key => $value)
|
||||
foreach ($request->except(self::INTERNAL_POST) as $key => $value)
|
||||
$o->{$key} = array_filter($value);
|
||||
|
||||
// We need to process and encrypt the password
|
||||
if ($request->userpassword)
|
||||
$o->userpassword = $this->password(
|
||||
$o->getObject('userpassword'),
|
||||
$request->userpassword,
|
||||
$request->get('_userpassword_hash'));
|
||||
|
||||
try {
|
||||
$o->save();
|
||||
|
||||
@ -146,6 +159,12 @@ class HomeController extends Controller
|
||||
));
|
||||
}
|
||||
|
||||
// If there are an _auto_value attributes, we need to invalid those
|
||||
foreach ($request->get('_auto_value',[]) as $attr => $value) {
|
||||
Log::debug(sprintf('%s:Removing auto_value attr [%s]',self::LOGKEY,$attr));
|
||||
Cache::delete($attr.':'.Session::id());
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->withFragment($o->getDNSecure());
|
||||
}
|
||||
@ -187,7 +206,7 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->with('success',[sprintf('%s: %s',__('Deleted'),$dn)]);
|
||||
->with('success',sprintf('%s: %s',__('Deleted'),$dn));
|
||||
}
|
||||
|
||||
public function entry_export(Request $request,string $id): \Illuminate\View\View
|
||||
@ -211,7 +230,7 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function entry_objectclass_add(Request $request): Collection
|
||||
{
|
||||
$dn = $request->key ? Crypt::decryptString($request->dn) : '';
|
||||
$dn = $request->get('_key') ? Crypt::decryptString($request->dn) : '';
|
||||
$oc = Factory::create($dn,'objectclass',$request->oc);
|
||||
|
||||
$ocs = $oc
|
||||
@ -266,43 +285,53 @@ class HomeController extends Controller
|
||||
|
||||
$o = config('server')->fetch($dn);
|
||||
|
||||
foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value)
|
||||
foreach ($request->except(['_token','dn','_userpassword_hash','userpassword']) as $key => $value)
|
||||
$o->{$key} = array_filter($value,fn($item)=>! is_null($item));
|
||||
|
||||
// @todo Need to handle incoming attributes that were modified by MD5Updates Trait (eg: jpegphoto)
|
||||
|
||||
// We need to process and encrypt the password
|
||||
if ($request->userpassword) {
|
||||
$passwords = [];
|
||||
$po = $o->getObject('userpassword');
|
||||
foreach (Arr::dot($request->userpassword) as $dotkey => $value) {
|
||||
// If the password is still the MD5 of the old password, then it hasnt changed
|
||||
if (($old=Arr::get($po,$dotkey)) && ($value === md5($old))) {
|
||||
$passwords[$dotkey] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
$type = Arr::get($request->userpassword_hash,$dotkey);
|
||||
$passwords[$dotkey] = Password::hash_id($type)
|
||||
->encode($value);
|
||||
}
|
||||
}
|
||||
|
||||
$o->userpassword = Arr::undot($passwords);
|
||||
}
|
||||
if ($request->userpassword)
|
||||
$o->userpassword = $this->password(
|
||||
$o->getObject('userpassword'),
|
||||
$request->userpassword,
|
||||
$request->get('_userpassword_hash'));
|
||||
|
||||
if (! $o->getDirty())
|
||||
return back()
|
||||
return Redirect::back()
|
||||
->withInput()
|
||||
->with('note',__('No attributes changed'));
|
||||
|
||||
return view('update')
|
||||
->with('bases',$this->bases())
|
||||
->with('dn',$dn)
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function entry_rename(Request $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
||||
{
|
||||
$from_dn = Crypt::decryptString($request->post('dn'));
|
||||
Log::info(sprintf('%s:Renaming [%s] to [%s]',self::LOGKEY,$from_dn,$request->post('_rdn_new')));
|
||||
|
||||
$o = config('server')->fetch($from_dn);
|
||||
|
||||
if (! $o)
|
||||
return Redirect::back()
|
||||
->withInput()
|
||||
->with('note',__('DN doesnt exist'));
|
||||
|
||||
try {
|
||||
$o->rename($request->post('_rdn_new'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return Redirect::to('/')
|
||||
->with('failed',$e->getMessage());
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->withInput(['_key'=>Crypt::encryptString('*dn|'.$o->getDN())])
|
||||
->with('success',sprintf('%s: %s',__('Entry renamed'),$from_dn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a DN entry
|
||||
*
|
||||
@ -322,7 +351,7 @@ class HomeController extends Controller
|
||||
$o->{$key} = array_filter($value);
|
||||
|
||||
if (! $dirty=$o->getDirty())
|
||||
return back()
|
||||
return Redirect::back()
|
||||
->withInput()
|
||||
->with('note',__('No attributes changed'));
|
||||
|
||||
@ -356,7 +385,9 @@ class HomeController extends Controller
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->with('updated',collect($dirty)
|
||||
->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
|
||||
->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first()))
|
||||
->values()
|
||||
->unique());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -366,6 +397,7 @@ class HomeController extends Controller
|
||||
* @param Request $request
|
||||
* @param Collection|null $old
|
||||
* @return \Illuminate\View\View
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function frame(Request $request,?Collection $old=NULL): \Illuminate\View\View
|
||||
{
|
||||
@ -375,22 +407,28 @@ class HomeController extends Controller
|
||||
|
||||
$key = $this->request_key($request,$old);
|
||||
|
||||
$view = ($old
|
||||
$view = $old
|
||||
? view('frame')->with('subframe',$key['cmd'])
|
||||
: view('frames.'.$key['cmd']))
|
||||
->with('bases',$this->bases());
|
||||
: view('frames.'.$key['cmd']);
|
||||
|
||||
// If we are rendering a DN, rebuild our object
|
||||
if ($key['dn']) {
|
||||
if ($key['cmd'] === 'create') {
|
||||
$o = new Entry;
|
||||
$o->setRDNBase($key['dn']);
|
||||
|
||||
} elseif ($key['dn']) {
|
||||
// @todo Need to handle if DN is null, for example if the user's session expired and the ACLs dont let them retrieve $key['dn']
|
||||
$o = config('server')->fetch($key['dn']);
|
||||
|
||||
foreach (collect(old())->except(['key','dn','step','_token','userpassword_hash','rdn','rdn_value']) as $attr => $value)
|
||||
foreach (collect(old())->except(array_merge(self::INTERNAL_POST,['dn'])) as $attr => $value)
|
||||
$o->{$attr} = $value;
|
||||
}
|
||||
|
||||
return match ($key['cmd']) {
|
||||
'create' => $view
|
||||
->with('container',old('container',$key['dn']))
|
||||
->with('o',$o)
|
||||
->with('template',NULL)
|
||||
->with('step',1),
|
||||
|
||||
'dn' => $view
|
||||
@ -399,7 +437,7 @@ class HomeController extends Controller
|
||||
->with('page_actions',collect([
|
||||
'copy'=>FALSE,
|
||||
'create'=>($x=($o->getObjects()->except('entryuuid')->count() > 0)),
|
||||
'delete'=>$x,
|
||||
'delete'=>(! is_null($xx=$o->getObject('hassubordinates')->value)) && ($xx === 'FALSE'),
|
||||
'edit'=>$x,
|
||||
'export'=>$x,
|
||||
])),
|
||||
@ -418,8 +456,7 @@ class HomeController extends Controller
|
||||
// Did we come here as a result of a redirect
|
||||
return count(old())
|
||||
? $this->frame($request,collect(old()))
|
||||
: view('home')
|
||||
->with('bases',$this->bases());
|
||||
: view('home');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -455,11 +492,32 @@ class HomeController extends Controller
|
||||
|
||||
return view('frame')
|
||||
->with('subframe','import_result')
|
||||
->with('bases',$this->bases())
|
||||
->with('result',$result)
|
||||
->with('ldif',htmlspecialchars($x));
|
||||
}
|
||||
|
||||
private function password(Password $po,array $values,array $hash): array
|
||||
{
|
||||
// We need to process and encrypt the password
|
||||
$passwords = [];
|
||||
|
||||
foreach (Arr::dot($values) as $dotkey => $value) {
|
||||
// If the password is still the MD5 of the old password, then it hasnt changed
|
||||
if (($old=Arr::get($po,$dotkey)) && ($value === md5($old))) {
|
||||
$passwords[$dotkey] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
$type = Arr::get($hash,$dotkey);
|
||||
$passwords[$dotkey] = Password::hash_id($type)
|
||||
->encode($value);
|
||||
}
|
||||
}
|
||||
|
||||
return Arr::undot($passwords);
|
||||
}
|
||||
|
||||
/**
|
||||
* For any incoming request, work out the command and DN involved
|
||||
*
|
||||
@ -472,8 +530,8 @@ class HomeController extends Controller
|
||||
// Setup
|
||||
$cmd = NULL;
|
||||
$dn = NULL;
|
||||
$key = $request->get('key',old('key'))
|
||||
? Crypt::decryptString($request->get('key',old('key')))
|
||||
$key = ($x=$request->get('_key',old('_key')))
|
||||
? Crypt::decryptString($x)
|
||||
: NULL;
|
||||
|
||||
// Determine if our key has a command
|
||||
@ -485,9 +543,9 @@ class HomeController extends Controller
|
||||
$dn = ($m[2] !== '_NOP') ? $m[2] : NULL;
|
||||
}
|
||||
|
||||
} elseif (old('dn',$request->get('key'))) {
|
||||
} elseif ($x=old('dn',$request->get('_key'))) {
|
||||
$cmd = 'dn';
|
||||
$dn = Crypt::decryptString(old('dn',$request->get('key')));
|
||||
$dn = Crypt::decryptString($x);
|
||||
}
|
||||
|
||||
return ['cmd'=>$cmd,'dn'=>$dn];
|
||||
@ -504,12 +562,12 @@ class HomeController extends Controller
|
||||
public function schema_frame(Request $request): \Illuminate\View\View
|
||||
{
|
||||
// If an invalid key, we'll 404
|
||||
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
|
||||
if ($request->type && $request->get('_key') && (! config('server')->schema($request->type)->has($request->get('_key'))))
|
||||
abort(404);
|
||||
|
||||
return view('frames.schema')
|
||||
->with('type',$request->type)
|
||||
->with('key',$request->key);
|
||||
->with('key',$request->get('_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ class SearchController extends Controller
|
||||
|
||||
$result = collect();
|
||||
|
||||
foreach ($so->baseDNs() as $base) {
|
||||
foreach ($so->baseDNs(FALSE) as $base) {
|
||||
$search = (new Entry)
|
||||
->in($base);
|
||||
|
||||
|
46
app/Http/Middleware/AcceptLanguage.php
Normal file
46
app/Http/Middleware/AcceptLanguage.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AcceptLanguage
|
||||
{
|
||||
private const LOGKEY = 'MAL';
|
||||
|
||||
public function handle(Request $request,Closure $next): mixed
|
||||
{
|
||||
if ($locale=$this->parseHttpLocale($request)) {
|
||||
Log::debug(sprintf('%s:Accept Language changed from [%s] to [%s] from Browser (%s)',self::LOGKEY,app()->getLocale(),$locale,$request->header('Accept-Language')));
|
||||
|
||||
app()->setLocale($locale);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function parseHttpLocale(Request $request): string
|
||||
{
|
||||
$list = explode(',',$request->server('HTTP_ACCEPT_LANGUAGE',''));
|
||||
|
||||
$locales = Collection::make($list)
|
||||
->map(function ($locale) {
|
||||
$parts = explode(';',$locale);
|
||||
$mapping = [];
|
||||
|
||||
$mapping['locale'] = trim($parts[0]);
|
||||
$mapping['factor'] = isset($parts[1])
|
||||
? Arr::get(explode('=',$parts[1]),1)
|
||||
: 1;
|
||||
|
||||
return $mapping;
|
||||
})
|
||||
->sortByDesc(fn($locale)=>$locale['factor']);
|
||||
|
||||
return Arr::get($locales->first(),'locale');
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
|
||||
/**
|
||||
* This sets up our application session with any required values, ultimately for cache optimisation reasons
|
||||
*/
|
||||
class ApplicationSession
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request,Closure $next): mixed
|
||||
{
|
||||
Config::set('server',new Server);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
@ -29,7 +30,7 @@ class SwapinAuthUser
|
||||
if (! array_key_exists($key,config('ldap.connections')))
|
||||
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
|
||||
|
||||
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
|
||||
if (($request->path() !== 'logout') && 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')));
|
||||
|
||||
@ -43,6 +44,8 @@ class SwapinAuthUser
|
||||
$c->setConfiguration(config('ldap.connections.'.$key));
|
||||
$c->setGuardResolver(fn()=>new Guard($c->getLdapConnection(),$c->getConfiguration()));
|
||||
|
||||
Config::set('server',new Server);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -3,12 +3,17 @@
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
use App\Rules\{DNExists,HasStructuralObjectClass};
|
||||
|
||||
class EntryAddRequest extends FormRequest
|
||||
{
|
||||
private const LOGKEY = 'EAR';
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
@ -17,8 +22,8 @@ class EntryAddRequest extends FormRequest
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'rdn' => __('RDN is required.'),
|
||||
'rdn_value' => __('RDN value is required.'),
|
||||
'_rdn' => __('RDN is required.'),
|
||||
'_rdn_value' => __('RDN value is required.'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -35,14 +40,23 @@ class EntryAddRequest extends FormRequest
|
||||
return [];
|
||||
|
||||
$r = request() ?: collect();
|
||||
$rk = array_keys($r->all());
|
||||
|
||||
return config('server')
|
||||
->schema('attributetypes')
|
||||
->intersectByKeys($r->all())
|
||||
->filter(fn($item)=>$item->names_lc->intersect($rk)->count())
|
||||
->transform(function($item) use ($rk) {
|
||||
// Set the attributetype name
|
||||
if (($x=$item->names_lc->intersect($rk))->count() === 1)
|
||||
$item->setName($x->pop());
|
||||
|
||||
return $item;
|
||||
})
|
||||
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
|
||||
->filter()
|
||||
->flatMap(fn($item)=>$item)
|
||||
->merge([
|
||||
'key' => [
|
||||
'_key' => [
|
||||
'required',
|
||||
new DNExists,
|
||||
function (string $attribute,mixed $value,\Closure $fail) {
|
||||
@ -57,20 +71,68 @@ class EntryAddRequest extends FormRequest
|
||||
}
|
||||
},
|
||||
],
|
||||
'rdn' => 'required_if:step,2|string|min:1',
|
||||
'rdn_value' => 'required_if:step,2|string|min:1',
|
||||
'step' => 'int|min:1|max:2',
|
||||
'_rdn' => 'required_if:_step,2|string|min:1',
|
||||
'_rdn_value' => 'required_if:_step,2|string|min:1',
|
||||
'_step' => 'int|min:1|max:2',
|
||||
'objectclass'=>[
|
||||
'required',
|
||||
'array',
|
||||
'min:1',
|
||||
'max:1',
|
||||
],
|
||||
'objectclass._null_'=>[
|
||||
'required',
|
||||
'objectclass._null_' => [
|
||||
function (string $attribute,mixed $value,\Closure $fail) {
|
||||
$oc = collect($value)->dot()->filter();
|
||||
|
||||
// If this is step 1 and there is no objectclass, and no template, then fail
|
||||
if ((! $oc->count())
|
||||
&& (request()->post('_step') == 1)
|
||||
&& (! request()->post('template')))
|
||||
{
|
||||
$fail(__('Select an objectclass or a template'));
|
||||
}
|
||||
|
||||
// Cant have both an objectclass and a template
|
||||
if (request()->post('template') && $oc->count())
|
||||
$fail(__('You cannot select a template and an objectclass'));
|
||||
},
|
||||
'array',
|
||||
'min:1',
|
||||
new HasStructuralObjectClass,
|
||||
],
|
||||
'template' => [
|
||||
function (string $attribute,mixed $value,\Closure $fail) {
|
||||
$oc = collect(request()->post('objectclass'))->dot()->filter();
|
||||
|
||||
// If this is step 1 and there is no objectclass, and no template, then fail
|
||||
if ((! collect($value)->filter()->count())
|
||||
&& (request()->post('_step') == 1)
|
||||
&& (! $oc->count()))
|
||||
{
|
||||
$fail(__('Select an objectclass or a template'));
|
||||
}
|
||||
|
||||
// Cant have both an objectclass and a template
|
||||
if ($oc->count() && strlen($value))
|
||||
$fail(__('You cannot select a template and an objectclass'));
|
||||
},
|
||||
],
|
||||
'_auto_value' => 'nullable|array|min:1',
|
||||
'_auto_value.*' => [
|
||||
'nullable',
|
||||
function (string $attribute,mixed $value,\Closure $fail) {
|
||||
$attr = preg_replace('/^_auto_value\./','',$attribute);
|
||||
|
||||
// If the value has been overritten, then our auto_value is invalid
|
||||
if (! collect(request()->get($attr))->dot()->contains($value))
|
||||
return;
|
||||
|
||||
$cache = Cache::get($attr.':'.Session::id());
|
||||
Log::debug(sprintf('%s:Autovalue for Attribute [%s] in Session [%s] Retrieved [%d](%d)',self::LOGKEY,$attr,Session::id(),$cache,$value));
|
||||
|
||||
if ($cache !== (int)$value)
|
||||
$fail(__('Lock expired, please re-submit.'));
|
||||
}
|
||||
]
|
||||
])
|
||||
->toArray();
|
||||
|
@ -10,14 +10,24 @@ class EntryRequest extends FormRequest
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$r = request() ?: collect();
|
||||
$rk = array_keys($r->all());
|
||||
|
||||
return config('server')
|
||||
->schema('attributetypes')
|
||||
->intersectByKeys($r->all())
|
||||
->filter(fn($item)=>$item->names_lc->intersect($rk)->count())
|
||||
->transform(function($item) use ($rk) {
|
||||
// Set the attributetype name
|
||||
if (($x=$item->names_lc->intersect($rk))->count() === 1)
|
||||
$item->setName($x->pop());
|
||||
|
||||
return $item;
|
||||
})
|
||||
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
|
||||
->filter()
|
||||
->flatMap(fn($item)=>$item)
|
||||
|
@ -3,10 +3,16 @@
|
||||
namespace App\Ldap;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use LdapRecord\Support\Arr;
|
||||
use LdapRecord\Models\Model;
|
||||
|
||||
use App\Classes\Template;
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Classes\LDAP\Attribute\Factory;
|
||||
use App\Classes\LDAP\Export\LDIF;
|
||||
@ -21,11 +27,16 @@ use App\Exceptions\InvalidUsage;
|
||||
class Entry extends Model
|
||||
{
|
||||
private const TAG_CHARS = 'a-zA-Z0-9-';
|
||||
private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']';
|
||||
public const LANG_TAG_PREFIX = 'lang-';
|
||||
public const TAG_CHARS_LANG = self::LANG_TAG_PREFIX.'['.self::TAG_CHARS.']+';
|
||||
public const TAG_NOTAG = '_null_';
|
||||
|
||||
// Our Attribute objects
|
||||
private Collection $objects;
|
||||
|
||||
// Templates that apply to this entry
|
||||
private(set) Collection $templates;
|
||||
|
||||
// For new entries, this is the container that this entry will be stored in
|
||||
private string $rdnbase;
|
||||
|
||||
@ -33,9 +44,31 @@ class Entry extends Model
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->objects = collect();
|
||||
|
||||
parent::__construct($attributes);
|
||||
// Load any templates
|
||||
$this->templates = Cache::remember('templates'.Session::id(),config('ldap.cache.time'),function() {
|
||||
$template_dir = Storage::disk(config('pla.template.dir'));
|
||||
$templates = collect();
|
||||
|
||||
foreach (array_filter($template_dir->files('.',TRUE),fn($item)=>Str::endsWith($item,'.json')) as $file) {
|
||||
if (config('pla.template.exclude_system',FALSE) && Str::doesntContain($file,'/'))
|
||||
continue;
|
||||
|
||||
$to = new Template($file);
|
||||
|
||||
if ($to->invalid) {
|
||||
Log::debug(sprintf('Template [%s] is not valid (%s) - ignoring',$file,$to->reason));
|
||||
|
||||
} else {
|
||||
$templates->put($file,new Template($file));
|
||||
}
|
||||
}
|
||||
|
||||
return $templates;
|
||||
});
|
||||
}
|
||||
|
||||
public function discardChanges(): static
|
||||
@ -125,6 +158,17 @@ class Entry extends Model
|
||||
$this->objects = collect();
|
||||
}
|
||||
|
||||
// Filter out our templates specific for this entry
|
||||
if ($this->dn && (! in_array(strtolower($this->dn),['cn=subschema']))) {
|
||||
$this->templates = $this->templates
|
||||
->filter(fn($item)=>$item->enabled
|
||||
&& (! $item->objectclasses
|
||||
->map('strtolower')
|
||||
->diff(array_map('strtolower',Arr::get($this->attributes,'objectclass')))
|
||||
->count()))
|
||||
->sortBy(fn($item)=>$item->title);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -164,13 +208,13 @@ class Entry extends Model
|
||||
// If the attribute name has tags
|
||||
list($attribute,$tag) = $this->keytag($key);
|
||||
|
||||
if (! config('server')->schema('attributetypes')->has($attribute))
|
||||
if (config('server')->get_attr_id($attribute) === FALSE)
|
||||
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
|
||||
|
||||
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]);
|
||||
$o->addValue($tag,[$value]);
|
||||
|
||||
$this->objects->put($attribute,$o);
|
||||
$this->objects->put($key,$o);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -274,7 +318,7 @@ class Entry extends Model
|
||||
$result = collect();
|
||||
|
||||
foreach (($this->getObject('objectclass')?->values ?: []) as $oc)
|
||||
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
|
||||
$result = $result->merge(config('server')->schema('objectclasses',$oc)->all_attributes);
|
||||
|
||||
return $result;
|
||||
}
|
||||
@ -308,14 +352,7 @@ class Entry extends Model
|
||||
public function getLangTags(): Collection
|
||||
{
|
||||
return $this->getObjects()
|
||||
->filter(fn($item)=>! $item->no_attr_tags)
|
||||
->map(fn($item)=>$item
|
||||
->values
|
||||
->keys()
|
||||
->filter(fn($item)=>preg_match(sprintf('/%s+;?/',self::TAG_CHARS_LANG),$item))
|
||||
->map(fn($item)=>preg_replace('/lang-/','',$item))
|
||||
)
|
||||
->filter(fn($item)=>$item->count());
|
||||
->map(fn($item)=>$item->langtags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,7 +410,7 @@ class Entry extends Model
|
||||
$item && collect(explode(';',$item))->filter(
|
||||
fn($item)=>
|
||||
(! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item))
|
||||
&& (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
|
||||
&& (! preg_match(sprintf('/^%s$/',self::TAG_CHARS_LANG),$item))
|
||||
)
|
||||
->count())
|
||||
)
|
||||
@ -384,9 +421,6 @@ class Entry extends Model
|
||||
* Return a list of attributes without any values
|
||||
*
|
||||
* @return Collection
|
||||
* @todo Dont show attributes that are not provided by an objectclass, make a new function to show those
|
||||
* This is for dynamic list items eg: labeledURI, which are not editable.
|
||||
* We can highlight those values that are as a result of a dynamic module
|
||||
*/
|
||||
public function getMissingAttributes(): Collection
|
||||
{
|
||||
@ -399,7 +433,7 @@ class Entry extends Model
|
||||
$o = new Attribute\RDN('','dn',['']);
|
||||
// @todo for an existing object, rdnbase would be null, so dynamically get it from the DN.
|
||||
$o->setBase($this->rdnbase);
|
||||
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
|
||||
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->is_must));
|
||||
|
||||
return $o;
|
||||
}
|
||||
@ -431,6 +465,21 @@ class Entry extends Model
|
||||
->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Did this query generate a size limit exception
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LdapRecord\ContainerException
|
||||
*/
|
||||
public function hasMore(): bool
|
||||
{
|
||||
return $this->getConnectionContainer()
|
||||
->getConnection()
|
||||
->getLdapConnection()
|
||||
->getDetailedError()
|
||||
?->getErrorCode() === 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an icon for a DN based on objectClass
|
||||
*
|
||||
@ -531,5 +580,13 @@ class Entry extends Model
|
||||
throw new InvalidUsage('Cannot set RDN base on existing entries');
|
||||
|
||||
$this->rdnbase = $bdn;
|
||||
|
||||
$this->templates = $this->templates
|
||||
->filter(fn($item)=>(! $item->regexp) || preg_match($item->regexp,$bdn));
|
||||
}
|
||||
|
||||
public function template(string $item): Template|Null
|
||||
{
|
||||
return Arr::get($this->templates,$item);
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use LdapRecord\Configuration\DomainConfiguration;
|
||||
use LdapRecord\Laravel\LdapRecord;
|
||||
@ -29,11 +28,5 @@ class AppServiceProvider extends ServiceProvider
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
||||
|
||||
// Enable pluck on collections to work on private values
|
||||
Collection::macro('ppluck',
|
||||
fn($attr)=>$this
|
||||
->map(fn($item)=>$item->{$attr})
|
||||
->values());
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ class HasStructuralObjectClass implements ValidationRule
|
||||
if ($item && config('server')->schema('objectclasses',$item)->isStructural())
|
||||
return;
|
||||
|
||||
$fail('There isnt a Structural Objectclass.');
|
||||
if (collect($value)->dot()->filter()->count())
|
||||
$fail(__('There isnt a Structural Objectclass.'));
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
use App\Classes\LDAP\Attribute as LDAPAttribute;
|
||||
use App\Ldap\Entry;
|
||||
use App\Classes\Template;
|
||||
|
||||
class Attribute extends Component
|
||||
{
|
||||
@ -14,19 +14,20 @@ class Attribute extends Component
|
||||
public bool $edit;
|
||||
public bool $new;
|
||||
public bool $old;
|
||||
public string $langtag;
|
||||
public bool $updated;
|
||||
public ?Template $template;
|
||||
|
||||
/**
|
||||
* 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,bool $updated=FALSE,?Template $template=NULL)
|
||||
{
|
||||
$this->o = $o;
|
||||
$this->edit = $edit;
|
||||
$this->old = $old;
|
||||
$this->new = $new;
|
||||
$this->langtag = $langtag;
|
||||
$this->updated = $updated;
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +39,12 @@ 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)
|
||||
->render(
|
||||
edit: $this->edit,
|
||||
old: $this->old,
|
||||
new: $this->new,
|
||||
updated: $this->updated,
|
||||
template: $this->template)
|
||||
: __('Unknown');
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ 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\{AcceptLanguage,AllowAnonymous,CheckUpdate,SwapinAuthUser,ViewVariables};
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
@ -16,7 +16,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
$middleware->appendToGroup(
|
||||
group: 'web',
|
||||
middleware: [
|
||||
ApplicationSession::class,
|
||||
AcceptLanguage::class,
|
||||
AllowAnonymous::class,
|
||||
SwapinAuthUser::class,
|
||||
ViewVariables::class,
|
||||
|
@ -10,11 +10,12 @@
|
||||
"ext-openssl": "*",
|
||||
"php": "^8.4",
|
||||
"directorytree/ldaprecord-laravel": "^3.0",
|
||||
"laravel/framework": "^11.9",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/ui": "^4.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"amirami/localizator": "^0.14@dev",
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"mockery/mockery": "^1.6",
|
||||
|
614
composer.lock
generated
614
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -43,19 +43,6 @@ return [
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|
10
config/filesystems.php
Normal file
10
config/filesystems.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'disks' => [
|
||||
'templates' => [
|
||||
'driver' => 'local',
|
||||
'root' => base_path(env('LDAP_TEMPLATE_DIR','templates')),
|
||||
],
|
||||
],
|
||||
];
|
@ -35,7 +35,6 @@ return [
|
||||
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
|
||||
'password' => env('LDAP_PASSWORD', 'secret'),
|
||||
'port' => env('LDAP_PORT', 389),
|
||||
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
|
||||
'timeout' => env('LDAP_TIMEOUT', 5),
|
||||
'use_ssl' => env('LDAP_SSL', false),
|
||||
'use_tls' => env('LDAP_TLS', false),
|
||||
@ -47,7 +46,6 @@ return [
|
||||
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
|
||||
'password' => env('LDAP_PASSWORD', 'secret'),
|
||||
'port' => env('LDAP_PORT', 636),
|
||||
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
|
||||
'timeout' => env('LDAP_TIMEOUT', 5),
|
||||
'use_ssl' => env('LDAP_SSL', true),
|
||||
'use_tls' => env('LDAP_TLS', false),
|
||||
@ -59,7 +57,6 @@ return [
|
||||
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
|
||||
'password' => env('LDAP_PASSWORD', 'secret'),
|
||||
'port' => env('LDAP_PORT', 389),
|
||||
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
|
||||
'timeout' => env('LDAP_TIMEOUT', 5),
|
||||
'use_ssl' => env('LDAP_SSL', false),
|
||||
'use_tls' => env('LDAP_TLS', true),
|
||||
@ -72,7 +69,6 @@ return [
|
||||
'username' => 'cn=Directory Manager',
|
||||
'password' => 'password',
|
||||
'port' => 1389,
|
||||
'base_dn' => 'dc=example,dc=com',
|
||||
'timeout' => env('LDAP_TIMEOUT', 5),
|
||||
'use_ssl' => env('LDAP_SSL', false),
|
||||
'use_tls' => env('LDAP_TLS', false),
|
||||
|
59
config/localizator.php
Normal file
59
config/localizator.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* Localize types of translation strings.
|
||||
*/
|
||||
'localize' => [
|
||||
/**
|
||||
* Short keys. This is the default for Laravel.
|
||||
* They are stored in PHP files inside folders name by their locale code.
|
||||
* Laravel comes with default: auth.php, pagination.php, passwords.php and validation.php
|
||||
*/
|
||||
'default' => true,
|
||||
|
||||
/**
|
||||
* Translations strings as key.
|
||||
* They are stored in JSON file for each locale.
|
||||
*/
|
||||
'json' => true,
|
||||
],
|
||||
|
||||
/**
|
||||
* Search criteria for files.
|
||||
*/
|
||||
'search' => [
|
||||
/**
|
||||
* Directories which should be looked inside.
|
||||
*/
|
||||
'dirs' => ['app','resources/views'],
|
||||
|
||||
/**
|
||||
* Subdirectories which will be excluded.
|
||||
* The values must be relative to the included directory paths.
|
||||
*/
|
||||
'exclude' => [
|
||||
//
|
||||
],
|
||||
|
||||
/**
|
||||
* Patterns by which files should be queried.
|
||||
* The values can be a regular expression, glob, or just a string.
|
||||
*/
|
||||
'patterns' => ['*.php'],
|
||||
|
||||
/**
|
||||
* Functions that the strings will be extracted from.
|
||||
* Add here any custom defined functions.
|
||||
* NOTE: The translation string should always be the first argument.
|
||||
*/
|
||||
'functions' => ['__', 'trans', '@lang']
|
||||
],
|
||||
|
||||
/**
|
||||
* Should the localize command sort extracted strings alphabetically?
|
||||
*/
|
||||
'sort' => true,
|
||||
|
||||
];
|
@ -43,6 +43,17 @@ return [
|
||||
|
||||
'allow_guest' => env('LDAP_ALLOW_GUEST',FALSE),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Base DNs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Normally PLA will get the base DNs from the rootDSE's namingcontexts
|
||||
| entry. Instead of using that, you can define your own base DNs to use.
|
||||
|
|
||||
*/
|
||||
'base_dns' => ($x=env('LDAP_BASE_DN', NULL)) ? explode(':',$x) : NULL,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Date Format
|
||||
@ -73,7 +84,20 @@ return [
|
||||
* setup.
|
||||
*/
|
||||
'login' => [
|
||||
'attr' => [env('LDAP_LOGIN_ATTR','uid') => env('LDAP_LOGIN_ATTR_DESC','User ID')], // Attribute used to find user for login
|
||||
'objectclass' => explode(',',env('LDAP_LOGIN_OBJECTCLASS', 'posixAccount')), // Objectclass that users must contain to login
|
||||
// Attribute used to find user for login
|
||||
'attr' => [strtolower(env('LDAP_LOGIN_ATTR','uid')) => env('LDAP_LOGIN_ATTR_DESC','User ID')],
|
||||
// Objectclass that users must contain to login
|
||||
'objectclass' => explode(',',env('LDAP_LOGIN_OBJECTCLASS', 'posixAccount')),
|
||||
// Alert if DN is being used, and the login fails, and the the DN doesnt exist
|
||||
'alert_rootdn' => env('LDAP_ALERT_ROOTDN',TRUE) && strtolower(env('LDAP_LOGIN_ATTR','uid')) === 'dn',
|
||||
],
|
||||
|
||||
'template' => [
|
||||
'dir' => env('LDAP_TEMPLATE_DRIVER','templates'),
|
||||
'exclude_system' => env('LDAP_TEMPLATE_EXCLUDE_SYSTEM',FALSE),
|
||||
'getnextnumber' => [
|
||||
'gidnumber' => env('LDAP_TEMPLATE_GIDNUMBER_START', 1000),
|
||||
'uidnumber' => env('LDAP_TEMPLATE_UIDNUMBER_START', 1000),
|
||||
],
|
||||
],
|
||||
];
|
1328
package-lock.json
generated
1328
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
v2.1.5-dev
|
||||
v2.2.1-rel
|
||||
|
29
public/css/custom.css
vendored
29
public/css/custom.css
vendored
@ -1,23 +1,33 @@
|
||||
/** ensure our userpassword has select is next to the password input */
|
||||
attribute#userPassword .select2-container--bootstrap-5 .select2-selection {
|
||||
attribute#userpassword .select2-container--bootstrap-5 .select2-selection {
|
||||
font-size: inherit;
|
||||
width: 9em;
|
||||
border: var(--bs-gray-500) 1px solid;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.input-group:first-child .select2-container--bootstrap-5 .select2-selection {
|
||||
border-bottom-right-radius: unset;
|
||||
border-top-right-radius: unset;
|
||||
}
|
||||
|
||||
attribute#objectClass .input-group-end:not(input.form-control) {
|
||||
/* render the structural inside the input box */
|
||||
attribute#objectclass .input-group-end:not(input.form-control) {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 0.5em;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* select forms that have nothing next to them */
|
||||
.select-group:first-child .select2-container--bootstrap-5 .select2-selection {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.input-group:first-child:not(.select-group) .select2-container--bootstrap-5 .select2-selection {
|
||||
border-bottom-right-radius: unset;
|
||||
border-top-right-radius: unset;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
input.form-control.input-group-end {
|
||||
border-bottom-right-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
@ -83,3 +93,8 @@ input.form-control.input-group-end {
|
||||
.page-title-wrapper .page-title-items .page-title-status .alert {
|
||||
font-size: 0.80em;
|
||||
}
|
||||
|
||||
/* Square UL items */
|
||||
ul.square {
|
||||
list-style-type: square;
|
||||
}
|
6
public/css/fixes.css
vendored
6
public/css/fixes.css
vendored
@ -263,3 +263,9 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
|
||||
body.modal-open {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Fix our search results, implementing a scroll bar */
|
||||
#search_results ul.typeahead.dropdown-menu {
|
||||
overflow-y: scroll;
|
||||
max-height: 300px;
|
||||
}
|
13
public/js/custom.js
vendored
13
public/js/custom.js
vendored
@ -15,7 +15,7 @@ function getNode(item) {
|
||||
$.ajax({
|
||||
url: '/frame',
|
||||
method: 'POST',
|
||||
data: { key: item },
|
||||
data: { _key: item },
|
||||
dataType: 'html',
|
||||
beforeSend: function() {
|
||||
content = $('.main-content')
|
||||
@ -37,11 +37,11 @@ function getNode(item) {
|
||||
$('.main-content').empty().append(e.responseText);
|
||||
break;
|
||||
case 409: // Not in root
|
||||
location.replace('/#'+item);
|
||||
break;
|
||||
case 419: // Session Expired
|
||||
location.replace('/#'+item);
|
||||
location.reload();
|
||||
// When the session expires, and we are in the tree, we need to force a reload
|
||||
if (location.pathname === '/')
|
||||
location.reload();
|
||||
break;
|
||||
case 500:
|
||||
case 555: // Missing Method
|
||||
@ -59,7 +59,7 @@ $(document).ready(function() {
|
||||
if (typeof basedn !== 'undefined') {
|
||||
sources = basedn;
|
||||
} else {
|
||||
sources = { url: 'ajax/bases' };
|
||||
sources = { method: 'POST', url: '/ajax/bases' };
|
||||
}
|
||||
|
||||
// Attach the fancytree widget to an existing <div id="tree"> element
|
||||
@ -95,8 +95,9 @@ $(document).ready(function() {
|
||||
source: sources,
|
||||
lazyLoad: function(event,data) {
|
||||
data.result = {
|
||||
method: 'POST',
|
||||
url: '/ajax/children',
|
||||
data: {key: data.node.data.item,depth: 1}
|
||||
data: {_key: data.node.data.item,create: true}
|
||||
};
|
||||
|
||||
expandChildren(data.tree.rootNode);
|
||||
|
23
public/js/template.js
vendored
Normal file
23
public/js/template.js
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/* JavaScript template engine abstraction layer */
|
||||
/* Currently implemented for jquery */
|
||||
|
||||
// Get a value from an attribute
|
||||
function get_attribute(attribute,start,end) {
|
||||
var val = $('#'+attribute).find('input').val();
|
||||
|
||||
return ((start !== undefined) && (end !== undefined))
|
||||
? val.substring(start,end)
|
||||
: val;
|
||||
}
|
||||
|
||||
// Put a value to an attribute
|
||||
function put_attribute(attribute,result) {
|
||||
// Get the value, if the value hasnt changed, then we dont need to do anything
|
||||
if (get_attribute(attribute) === result)
|
||||
return;
|
||||
|
||||
$('#'+attribute)
|
||||
.find('input')
|
||||
.val(result)
|
||||
.trigger('change');
|
||||
}
|
81
public/js/toAscii.js
vendored
Normal file
81
public/js/toAscii.js
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// Purpose of this file is to remap characters as ASCII characters
|
||||
//
|
||||
//
|
||||
|
||||
var to_ascii_array = new Array();
|
||||
to_ascii_array['Ã '] = 'a';
|
||||
to_ascii_array['á'] = 'a';
|
||||
to_ascii_array['â'] = 'a';
|
||||
to_ascii_array['À'] = 'a';
|
||||
to_ascii_array['ã'] = 'a';
|
||||
to_ascii_array['Ã¥'] = 'a';
|
||||
to_ascii_array['Ã'] = 'A';
|
||||
to_ascii_array['Ã'] = 'A';
|
||||
to_ascii_array['Ã'] = 'A';
|
||||
to_ascii_array['Ã'] = 'A';
|
||||
to_ascii_array['Ã'] = 'A';
|
||||
to_ascii_array['Ã
'] = 'A';
|
||||
to_ascii_array['é'] = 'e';
|
||||
to_ascii_array['Ú'] = 'e';
|
||||
to_ascii_array['ë'] = 'e';
|
||||
to_ascii_array['ê'] = 'e';
|
||||
to_ascii_array['â¬'] = 'E';
|
||||
to_ascii_array['ï'] = 'i';
|
||||
to_ascii_array['î'] = 'i';
|
||||
to_ascii_array['ì'] = 'i';
|
||||
to_ascii_array['Ã'] = 'i';
|
||||
to_ascii_array['Ã'] = 'I';
|
||||
to_ascii_array['Ã'] = 'I';
|
||||
to_ascii_array['Ã'] = 'I';
|
||||
to_ascii_array['Ã'] = 'I';
|
||||
to_ascii_array['ò'] = 'o';
|
||||
to_ascii_array['ó'] = 'o';
|
||||
to_ascii_array['ÃŽ'] = 'o';
|
||||
to_ascii_array['õ'] = 'o';
|
||||
to_ascii_array['ö'] = 'o';
|
||||
to_ascii_array['Þ'] = 'o';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['Ã'] = 'O';
|
||||
to_ascii_array['ù'] = 'u';
|
||||
to_ascii_array['ú'] = 'u';
|
||||
to_ascii_array['Ì'] = 'u';
|
||||
to_ascii_array['û'] = 'u';
|
||||
to_ascii_array['Ã'] = 'U';
|
||||
to_ascii_array['Ã'] = 'U';
|
||||
to_ascii_array['Ã'] = 'U';
|
||||
to_ascii_array['Ã'] = 'U';
|
||||
to_ascii_array['Ê'] = 'ae';
|
||||
to_ascii_array['Ã'] = 'AE';
|
||||
to_ascii_array['Ü'] = 'y';
|
||||
to_ascii_array['ÿ'] = 'y';
|
||||
to_ascii_array['Ã'] = 'SS';
|
||||
to_ascii_array['Ã'] = 'C';
|
||||
to_ascii_array['ç'] = 'c';
|
||||
to_ascii_array['Ã'] = 'N';
|
||||
to_ascii_array['ñ'] = 'n';
|
||||
to_ascii_array['¢'] = 'c';
|
||||
to_ascii_array['©'] = '(C)';
|
||||
to_ascii_array['®'] = '(R)';
|
||||
to_ascii_array['«'] = '<<';
|
||||
to_ascii_array['»'] = '>>';
|
||||
|
||||
function toAscii(text) {
|
||||
//var text = field.value;
|
||||
var output = '';
|
||||
|
||||
for (position=0; position < text.length; position++) {
|
||||
var tmp = text.substring(position,position+1);
|
||||
|
||||
if (to_ascii_array[tmp] !== undefined)
|
||||
tmp = to_ascii_array[tmp];
|
||||
|
||||
output += tmp;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
@ -1,6 +1,45 @@
|
||||
This directory contains language translation files for PLA.
|
||||
This directory contains language translation files for PLA. PLA should automatically detect your language based on your
|
||||
browser configuration, and if the language is not available it will fall back to the language used internally (English).
|
||||
|
||||
Language files named by 2 letter iso language name (suffixed with .json)
|
||||
represent the translations for that language.
|
||||
Language files are named by 2 letter iso language name (suffixed with .json) represent the translations for that
|
||||
language.
|
||||
Where a language is spoken in multiple countries, but has local country differences (eg: `en-US` vs `en-GB`,
|
||||
or `zh-CN` vs `zh-TW`), then the language filename is suffixed with `-` and a two letter country, eg:
|
||||
|
||||
eg: en.json
|
||||
* `en.json` for English (General),
|
||||
* `en-GB.json` for English (Great Britain),
|
||||
* `zh-CN.json` for Chinese (China),
|
||||
* `zh-TW.json` for Chinese (Taiwan), etc
|
||||
|
||||
The language file `zz.json` is an example language file, with each translated string prefixed with the letter "Z". Its
|
||||
used to identify any default language text (in english) that is not in a translated configuration. Text strings enclosed
|
||||
in `@lang()`, or `__()` functions are translatable to other languages.
|
||||
|
||||
If you want to update the language text for your language, then:
|
||||
|
||||
* If your language file **exists** (eg: `fr.json` for French), then:
|
||||
* Identify the missing tags (compare it to `zz.json`),
|
||||
* Insert the missing tags into the language file (eg: `fr.json` for French) - ensure you keep the file in English
|
||||
Alphabetical order.
|
||||
|
||||
* If your language file **doesnt** exist (eg; `fr.json` for French), then
|
||||
* Copy the default language file `zz.json` to `fr.json`
|
||||
* Translate the strings
|
||||
|
||||
The structure of the json files is:
|
||||
|
||||
```json
|
||||
{
|
||||
"Untranslated string1": "Translated string1",
|
||||
"Untranslated string2": "Translated string2"
|
||||
}
|
||||
```
|
||||
|
||||
Some important notes:
|
||||
* `Untranslated string` is the string as it appears in PLA, wrapped in either a `__()` or `@lang()` function, normally and english phrase
|
||||
* `Translated string` is the translation for your language
|
||||
* Each translated string must be comma terminated *EXCEPT* the last string
|
||||
|
||||
Please submit a pull request with your translations, so that others users can benefit from the translation.
|
||||
|
||||
If you find any strings that you are not translatable, or translated incorrectly, please submit a bug report.
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"Email": "DEV:Email",
|
||||
"Home": "DEV:Home",
|
||||
"Password": "DEV:Password",
|
||||
"Please enter your email": "DEV:Please enter your email",
|
||||
"Please enter your password": "DEV:Please enter your password",
|
||||
"Server Info": "DEV:Server Info",
|
||||
"Server Name": "DEV:Server Name",
|
||||
"Sign in to <strong>:server</strong>": "DEV:Sign in to <strong>:server</strong>"
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => 'These credentials do not match our records.',
|
||||
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
||||
|
||||
];
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« Previous',
|
||||
'next' => 'Next »',
|
||||
|
||||
];
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'reset' => 'Your password has been reset!',
|
||||
'sent' => 'We have emailed your password reset link!',
|
||||
'throttled' => 'Please wait before retrying.',
|
||||
'token' => 'This password reset token is invalid.',
|
||||
'user' => "We can't find a user with that email address.",
|
||||
|
||||
];
|
@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => 'The :attribute must be accepted.',
|
||||
'active_url' => 'The :attribute is not a valid URL.',
|
||||
'after' => 'The :attribute must be a date after :date.',
|
||||
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
|
||||
'alpha' => 'The :attribute may only contain letters.',
|
||||
'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
|
||||
'alpha_num' => 'The :attribute may only contain letters and numbers.',
|
||||
'array' => 'The :attribute must be an array.',
|
||||
'before' => 'The :attribute must be a date before :date.',
|
||||
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
|
||||
'between' => [
|
||||
'numeric' => 'The :attribute must be between :min and :max.',
|
||||
'file' => 'The :attribute must be between :min and :max kilobytes.',
|
||||
'string' => 'The :attribute must be between :min and :max characters.',
|
||||
'array' => 'The :attribute must have between :min and :max items.',
|
||||
],
|
||||
'boolean' => 'The :attribute field must be true or false.',
|
||||
'confirmed' => 'The :attribute confirmation does not match.',
|
||||
'date' => 'The :attribute is not a valid date.',
|
||||
'date_equals' => 'The :attribute must be a date equal to :date.',
|
||||
'date_format' => 'The :attribute does not match the format :format.',
|
||||
'different' => 'The :attribute and :other must be different.',
|
||||
'digits' => 'The :attribute must be :digits digits.',
|
||||
'digits_between' => 'The :attribute must be between :min and :max digits.',
|
||||
'dimensions' => 'The :attribute has invalid image dimensions.',
|
||||
'distinct' => 'The :attribute field has a duplicate value.',
|
||||
'email' => 'The :attribute must be a valid email address.',
|
||||
'ends_with' => 'The :attribute must end with one of the following: :values.',
|
||||
'exists' => 'The selected :attribute is invalid.',
|
||||
'file' => 'The :attribute must be a file.',
|
||||
'filled' => 'The :attribute field must have a value.',
|
||||
'gt' => [
|
||||
'numeric' => 'The :attribute must be greater than :value.',
|
||||
'file' => 'The :attribute must be greater than :value kilobytes.',
|
||||
'string' => 'The :attribute must be greater than :value characters.',
|
||||
'array' => 'The :attribute must have more than :value items.',
|
||||
],
|
||||
'gte' => [
|
||||
'numeric' => 'The :attribute must be greater than or equal :value.',
|
||||
'file' => 'The :attribute must be greater than or equal :value kilobytes.',
|
||||
'string' => 'The :attribute must be greater than or equal :value characters.',
|
||||
'array' => 'The :attribute must have :value items or more.',
|
||||
],
|
||||
'image' => 'The :attribute must be an image.',
|
||||
'in' => 'The selected :attribute is invalid.',
|
||||
'in_array' => 'The :attribute field does not exist in :other.',
|
||||
'integer' => 'The :attribute must be an integer.',
|
||||
'ip' => 'The :attribute must be a valid IP address.',
|
||||
'ipv4' => 'The :attribute must be a valid IPv4 address.',
|
||||
'ipv6' => 'The :attribute must be a valid IPv6 address.',
|
||||
'json' => 'The :attribute must be a valid JSON string.',
|
||||
'lt' => [
|
||||
'numeric' => 'The :attribute must be less than :value.',
|
||||
'file' => 'The :attribute must be less than :value kilobytes.',
|
||||
'string' => 'The :attribute must be less than :value characters.',
|
||||
'array' => 'The :attribute must have less than :value items.',
|
||||
],
|
||||
'lte' => [
|
||||
'numeric' => 'The :attribute must be less than or equal :value.',
|
||||
'file' => 'The :attribute must be less than or equal :value kilobytes.',
|
||||
'string' => 'The :attribute must be less than or equal :value characters.',
|
||||
'array' => 'The :attribute must not have more than :value items.',
|
||||
],
|
||||
'max' => [
|
||||
'numeric' => 'The :attribute may not be greater than :max.',
|
||||
'file' => 'The :attribute may not be greater than :max kilobytes.',
|
||||
'string' => 'The :attribute may not be greater than :max characters.',
|
||||
'array' => 'The :attribute may not have more than :max items.',
|
||||
],
|
||||
'mimes' => 'The :attribute must be a file of type: :values.',
|
||||
'mimetypes' => 'The :attribute must be a file of type: :values.',
|
||||
'min' => [
|
||||
'numeric' => 'The :attribute must be at least :min.',
|
||||
'file' => 'The :attribute must be at least :min kilobytes.',
|
||||
'string' => 'The :attribute must be at least :min characters.',
|
||||
'array' => 'The :attribute must have at least :min items.',
|
||||
],
|
||||
'not_in' => 'The selected :attribute is invalid.',
|
||||
'not_regex' => 'The :attribute format is invalid.',
|
||||
'numeric' => 'The :attribute must be a number.',
|
||||
'password' => 'The password is incorrect.',
|
||||
'present' => 'The :attribute field must be present.',
|
||||
'regex' => 'The :attribute format is invalid.',
|
||||
'required' => 'The :attribute field is required.',
|
||||
'required_if' => 'The :attribute field is required when :other is :value.',
|
||||
'required_unless' => 'The :attribute field is required unless :other is in :values.',
|
||||
'required_with' => 'The :attribute field is required when :values is present.',
|
||||
'required_with_all' => 'The :attribute field is required when :values are present.',
|
||||
'required_without' => 'The :attribute field is required when :values is not present.',
|
||||
'required_without_all' => 'The :attribute field is required when none of :values are present.',
|
||||
'same' => 'The :attribute and :other must match.',
|
||||
'size' => [
|
||||
'numeric' => 'The :attribute must be :size.',
|
||||
'file' => 'The :attribute must be :size kilobytes.',
|
||||
'string' => 'The :attribute must be :size characters.',
|
||||
'array' => 'The :attribute must contain :size items.',
|
||||
],
|
||||
'starts_with' => 'The :attribute must start with one of the following: :values.',
|
||||
'string' => 'The :attribute must be a string.',
|
||||
'timezone' => 'The :attribute must be a valid zone.',
|
||||
'unique' => 'The :attribute has already been taken.',
|
||||
'uploaded' => 'The :attribute failed to upload.',
|
||||
'url' => 'The :attribute format is invalid.',
|
||||
'uuid' => 'The :attribute must be a valid UUID.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'attribute-name' => [
|
||||
'rule-name' => 'custom-message',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap our attribute placeholder
|
||||
| with something more reader friendly such as "E-Mail Address" instead
|
||||
| of "email". This simply helps us make our message more expressive.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
];
|
143
resources/lang/zz.json
Normal file
143
resources/lang/zz.json
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"(no description)": "Z(no description)",
|
||||
"(none)": "Z(none)",
|
||||
"(not applicable)": "Z(not applicable)",
|
||||
"(not specified)": "Z(not specified)",
|
||||
"(unknown syntax)": "Z(unknown syntax)",
|
||||
"Add New Attribute": "ZAdd New Attribute",
|
||||
"Add Objectclass": "ZAdd Objectclass",
|
||||
"Add Value": "ZAdd Value",
|
||||
"Aliases": "ZAliases",
|
||||
"Attributes": "ZAttributes",
|
||||
"attributes(s)": "Zattributes(s)",
|
||||
"Attribute Types": "ZAttribute Types",
|
||||
"Authority Key Identifier": "ZAuthority Key Identifier",
|
||||
"Certificate Subject": "ZCertificate Subject",
|
||||
"Check": "ZCheck",
|
||||
"Check Password": "ZCheck Password",
|
||||
"Close": "ZClose",
|
||||
"Collective": "ZCollective",
|
||||
"Copy\/Move": "ZCopy\/Move",
|
||||
"Create Child Entry": "ZCreate Child Entry",
|
||||
"Created": "ZCreated",
|
||||
"Create Entry": "ZCreate Entry",
|
||||
"Create New Entry": "ZCreate New Entry",
|
||||
"Create new LDAP item here": "ZCreate new LDAP item here",
|
||||
"Delete": "ZDelete",
|
||||
"Deleted": "ZDeleted",
|
||||
"Delete Entry": "ZDelete Entry",
|
||||
"Deleting this DN will permanently delete it from your LDAP server.": "ZDeleting this DN will permanently delete it from your LDAP server.",
|
||||
"Description": "ZDescription",
|
||||
"DN": "ZDN",
|
||||
"Download": "ZDownload",
|
||||
"Do you want to make the following changes?": "ZDo you want to make the following changes?",
|
||||
"dynamic": "Zdynamic",
|
||||
"Edit Entry": "ZEdit Entry",
|
||||
"Entry": "ZEntry",
|
||||
"Entry updated": "ZEntry updated",
|
||||
"Equality": "ZEquality",
|
||||
"Error": "ZError",
|
||||
"Expired": "ZExpired",
|
||||
"Expires": "ZExpires",
|
||||
"Export": "ZExport",
|
||||
"Exported by": "ZExported by",
|
||||
"Force as MAY by config": "ZForce as MAY by config",
|
||||
"Generated by": "ZGenerated by",
|
||||
"Home": "ZHome",
|
||||
"Ignoring blank value": "ZIgnoring blank value",
|
||||
"Import Result": "ZImport Result",
|
||||
"Inherits from": "ZInherits from",
|
||||
"Internal": "ZInternal",
|
||||
"Invalid Password": "ZInvalid Password",
|
||||
"KRB_DISALLOW_ALL_TIX": "ZKRB_DISALLOW_ALL_TIX",
|
||||
"KRB_DISALLOW_DUP_SKEY": "ZKRB_DISALLOW_DUP_SKEY",
|
||||
"KRB_DISALLOW_FORWARDABLE": "ZKRB_DISALLOW_FORWARDABLE",
|
||||
"KRB_DISALLOW_POSTDATED": "ZKRB_DISALLOW_POSTDATED",
|
||||
"KRB_DISALLOW_PROXIABLE": "ZKRB_DISALLOW_PROXIABLE",
|
||||
"KRB_DISALLOW_RENEWABLE": "ZKRB_DISALLOW_RENEWABLE",
|
||||
"KRB_DISALLOW_SVR": "ZKRB_DISALLOW_SVR",
|
||||
"KRB_DISALLOW_TGT_BASED": "ZKRB_DISALLOW_TGT_BASED",
|
||||
"KRB_PWCHANGE_SERVICE": "ZKRB_PWCHANGE_SERVICE",
|
||||
"KRB_REQUIRES_HW_AUTH": "ZKRB_REQUIRES_HW_AUTH",
|
||||
"KRB_REQUIRES_PRE_AUTH": "ZKRB_REQUIRES_PRE_AUTH",
|
||||
"KRB_REQUIRES_PWCHANGE": "ZKRB_REQUIRES_PWCHANGE",
|
||||
"LDAP Authentication Error": "ZLDAP Authentication Error",
|
||||
"LDAP Entry": "ZLDAP Entry",
|
||||
"LDAP Server Error Code": "ZLDAP Server Error Code",
|
||||
"LDAP Server Unavailable": "ZLDAP Server Unavailable",
|
||||
"LDIF": "ZLDIF",
|
||||
"LDIF Import": "ZLDIF Import",
|
||||
"LDIF Import Result": "ZLDIF Import Result",
|
||||
"Line": "ZLine",
|
||||
"locale": "ZZ",
|
||||
"Matching Rules": "ZMatching Rules",
|
||||
"Maximum file size": "ZMaximum file size",
|
||||
"Maximum Length": "ZMaximum Length",
|
||||
"NEW": "ZNEW",
|
||||
"New Value": "ZNew Value",
|
||||
"Next": "ZNext",
|
||||
"No attributes changed": "ZNo attributes changed",
|
||||
"No description available, can you help with one?": "ZNo description available, can you help with one?",
|
||||
"No Server Name Yet": "ZNo Server Name Yet",
|
||||
"NOT DEFINED": "ZNOT DEFINED",
|
||||
"Not Implemented": "ZNot Implemented",
|
||||
"Object Classes": "ZObject Classes",
|
||||
"Object Identifier": "ZObject Identifier",
|
||||
"Obsolete": "ZObsolete",
|
||||
"Optional Attributes": "ZOptional Attributes",
|
||||
"Ordering": "ZOrdering",
|
||||
"Or upload LDIF file": "ZOr upload LDIF file",
|
||||
"Parent to": "ZParent to",
|
||||
"Paste in your LDIF here": "ZPaste in your LDIF here",
|
||||
"Possible Causes": "ZPossible Causes",
|
||||
"Process": "ZProcess",
|
||||
"rdn": "Zrdn",
|
||||
"RDN is required": "ZRDN is required",
|
||||
"RDN is required.": "ZRDN is required.",
|
||||
"RDN value is required.": "ZRDN value is required.",
|
||||
"Rename": "ZRename",
|
||||
"Replace": "ZReplace",
|
||||
"required": "Zrequired",
|
||||
"Required Attribute by ObjectClass(es)": "ZRequired Attribute by ObjectClass(es)",
|
||||
"Required Attributes": "ZRequired Attributes",
|
||||
"Required by ObjectClasses": "ZRequired by ObjectClasses",
|
||||
"Reset": "ZReset",
|
||||
"Result": "ZResult",
|
||||
"Schema Information": "ZSchema Information",
|
||||
"Search Filter": "ZSearch Filter",
|
||||
"Search Scope": "ZSearch Scope",
|
||||
"Select a Structural ObjectClass...": "ZSelect a Structural ObjectClass...",
|
||||
"Select attribute...": "ZSelect attribute...",
|
||||
"Select from": "ZSelect from",
|
||||
"Serial Number": "ZSerial Number",
|
||||
"Server": "ZServer",
|
||||
"Server Info": "ZServer Info",
|
||||
"Single Valued": "ZSingle Valued",
|
||||
"Step": "ZStep",
|
||||
"structural": "Zstructural",
|
||||
"Subject Key Identifier": "ZSubject Key Identifier",
|
||||
"Substring Rule": "ZSubstring Rule",
|
||||
"Syntax": "ZSyntax",
|
||||
"Syntaxes": "ZSyntaxes",
|
||||
"These are dynamic values present as a result of another attribute": "ZThese are dynamic values present as a result of another attribute",
|
||||
"This attribute is required for the RDN": "ZThis attribute is required for the RDN",
|
||||
"To Server": "ZTo Server",
|
||||
"Total Entries": "ZTotal Entries",
|
||||
"Type": "ZType",
|
||||
"Unknown": "ZUnknown",
|
||||
"Untrapped Error": "ZUntrapped Error",
|
||||
"Update": "ZUpdate",
|
||||
"Updated": "ZUpdated",
|
||||
"Upload JpegPhoto": "ZUpload JpegPhoto",
|
||||
"Usage": "ZUsage",
|
||||
"Used by Attributes": "ZUsed by Attributes",
|
||||
"Used by ObjectClasses": "ZUsed by ObjectClasses",
|
||||
"User Modification": "ZUser Modification",
|
||||
"Validation Errors": "ZValidation Errors",
|
||||
"Version": "ZVersion",
|
||||
"WARNING": "ZWARNING",
|
||||
"Your DNS server cannot resolve that hostname": "ZYour DNS server cannot resolve that hostname",
|
||||
"Your LDAP server hostname is incorrect": "ZYour LDAP server hostname is incorrect",
|
||||
"Your LDAP server is not connectable": "ZYour LDAP server is not connectable",
|
||||
"Your Resolver is not pointing to your DNS server": "ZYour Resolver is not pointing to your DNS server"
|
||||
}
|
@ -90,6 +90,14 @@
|
||||
font-size: 1.3rem !important;
|
||||
}
|
||||
|
||||
.font-size-xs {
|
||||
font-size: .6rem !important;
|
||||
}
|
||||
|
||||
.font-size-sm {
|
||||
font-size: .8rem !important;
|
||||
}
|
||||
|
||||
.font-size-md {
|
||||
font-size: .9rem !important;
|
||||
}
|
||||
|
@ -46,13 +46,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (count($errors) > 0)
|
||||
@if(count($errors) > 0)
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger m-3">
|
||||
<strong>Whoops!</strong> Something went wrong?<br><br>
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" translate="no">
|
||||
@section('htmlheader')
|
||||
@include('architect::layouts.partials.htmlheader')
|
||||
@show
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="app-main__outer">
|
||||
<div class="app-main__inner">
|
||||
<div class="main-content">
|
||||
@if (trim($__env->yieldContent('page_title')))
|
||||
@if(trim($__env->yieldContent('page_title')))
|
||||
@include('architect::layouts.partials.contentheader')
|
||||
@endif
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper bg-white">
|
||||
<div class="page-title-heading">
|
||||
@if (trim($__env->yieldContent('page_icon')))
|
||||
@if(trim($__env->yieldContent('page_icon')))
|
||||
<div class="page-title-icon f32">
|
||||
<i class="@yield('page_icon','')"></i>
|
||||
</div>
|
||||
|
@ -18,3 +18,8 @@
|
||||
<!-- Any Custom JS -->
|
||||
<script src="{{ asset('js/custom.js') }}"></script>
|
||||
@endif
|
||||
|
||||
@if(file_exists('js/template.js'))
|
||||
<!-- Template Engine JS -->
|
||||
<script src="{{ asset('js/template.js') }}"></script>
|
||||
@endif
|
@ -38,7 +38,7 @@
|
||||
<span></span>
|
||||
<div id="searching" class="d-none"><i class="fas fa-fw fa-spinner fa-pulse text-light"></i></div>
|
||||
</button>
|
||||
<div id="search_results" style="height: 300px; overflow: scroll"></div>
|
||||
<div id="search_results"></div>
|
||||
</div>
|
||||
<button class="btn-close"></button>
|
||||
</div>
|
||||
@ -139,10 +139,10 @@
|
||||
<div class="btn-group">
|
||||
<a data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="p-0 btn">
|
||||
<i class="fas fa-angle-down ms-2 opacity-8"></i>
|
||||
<img width="35" height="35" class="rounded-circle" src="{{ url('user/image') }}" alt="" style="background-color: #eee;padding: 2px;">
|
||||
<img width="35" height="35" class="rounded-circle p-1 bg-light" src="{{ url('user/image') }}" alt="">
|
||||
</a>
|
||||
<div tabindex="-1" role="menu" aria-hidden="true" class="dropdown-menu dropdown-menu-right">
|
||||
@if ($user->exists)
|
||||
@if($user->exists)
|
||||
<h6 tabindex="-1" class="dropdown-header text-center">User Menu</h6>
|
||||
<div tabindex="-1" class="dropdown-divider"></div>
|
||||
<a href="{{ url('logout') }}" tabindex="0" class="dropdown-item">
|
||||
@ -164,18 +164,14 @@
|
||||
</div>
|
||||
|
||||
@section('page-scripts')
|
||||
<style>
|
||||
#search_results ul.typeahead.dropdown-menu {
|
||||
overflow: scroll;
|
||||
max-height: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('button[id^="link-"]').on('click',function(item) {
|
||||
var content;
|
||||
|
||||
// Remove our fancy-tree highlight, since we are rendering the frame
|
||||
$('.fancytree-node.fancytree-active').removeClass('fancytree-active');
|
||||
|
||||
$.ajax({
|
||||
url: $(this).data('link'),
|
||||
method: 'GET',
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="alert alert-danger p-0" style="font-size: .80em;">
|
||||
<div class="alert alert-danger font-size-sm p-0">
|
||||
<table class="table table-borderless table-danger p-0 m-0">
|
||||
<tr>
|
||||
<td class="align-top" style="width: 5%;"><i class="fas fa-fw fa-2x fa-exclamation-triangle"></i></td>
|
||||
|
@ -1,23 +1,75 @@
|
||||
@use(App\Ldap\Entry)
|
||||
|
||||
<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">
|
||||
<div class="col-12 offset-lg-1 col-lg-10">
|
||||
<div class="row">
|
||||
<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
|
||||
<abbr title="{{ $description }}">{{ $name }}</abbr>
|
||||
@endforeach
|
||||
<div class="col-12 bg-light text-dark p-2 rounded-2">
|
||||
<span class="d-flex justify-content-between">
|
||||
<span style="width: 20em;">
|
||||
<strong class="align-middle"><abbr title="{{ (($x=$template?->attributeTitle($o->name)) ? $o->name.': ' : '').$o->description }}">{{ $x ?: $o->name }}</abbr></strong>
|
||||
@if($new)
|
||||
@if($template?->attributeReadOnly($o->name_lc))
|
||||
<sup data-bs-toggle="tooltip" title="@lang('Input disabled')"><i class="fas fa-ban"></i></sup>
|
||||
@endif
|
||||
@if($ca=$template?->onChangeAttribute($o->name_lc))
|
||||
<sup data-bs-toggle="tooltip" title="@lang('Value triggers an update to another attribute')"><i class="fas fa-keyboard"></i></sup>
|
||||
@endif
|
||||
@if ($ct=$template?->onChangeTarget($o->name_lc))
|
||||
<sup data-bs-toggle="tooltip" title="@lang('Value calculated from another attribute')"><i class="fas fa-wand-magic-sparkles"></i></sup>
|
||||
@endif
|
||||
@if((! $ca) && (! $ct) && $template?->attribute($o->name_lc))
|
||||
<sup data-bs-toggle="tooltip" title="@lang('Value controlled by template')"><i class="fas fa-wand-magic"></i></sup>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if($o->hints->count())
|
||||
<sup>
|
||||
[
|
||||
@foreach($o->hints as $name => $description)
|
||||
@if($loop->index),@endif
|
||||
<abbr title="{{ $description }}">{{ $name }}</abbr>
|
||||
@endforeach
|
||||
]
|
||||
</sup>
|
||||
@endif
|
||||
|
||||
<!-- Attribute Hints -->
|
||||
@if($updated)
|
||||
<span class=" 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>
|
||||
|
||||
<div role="group" class="btn-group-sm nav btn-group">
|
||||
@if((! $o->no_attr_tags) && ($has_default=$o->langtags->contains(Entry::TAG_NOTAG)))
|
||||
<span data-bs-toggle="tab" href="#langtag-{{ $o->name_lc }}-{{ Entry::TAG_NOTAG }}" @class(['btn','btn-outline-light','border-dark-subtle','active','addable d-none'=>$o->langtags->count() === 1])>
|
||||
<i class="fas fa-fw fa-border-none" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" aria-label="No Lang Tag" data-bs-original-title="No Lang Tag"></i>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if((! $o->no_attr_tags) && (! $o->is_rdn) && (! $template))
|
||||
<span data-bs-toggle="tab" href="#langtag-{{ $o->name_lc }}-+" class="bg-primary-subtle btn btn-outline-primary border-primary addable d-none">
|
||||
<i class="fas fa-fw fa-plus text-dark" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" aria-label="Add Lang Tag" data-bs-original-title="Add Lang Tag"></i>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@foreach(($langtags=$o->langtags->filter(fn($item)=>$item !== Entry::TAG_NOTAG)) as $langtag)
|
||||
<span data-bs-toggle="tab" href="#langtag-{{ $o->name_lc }}-{{ $langtag }}" @class(['btn','btn-outline-light','border-dark-subtle','active'=>(! isset($has_default)) || (! $has_default) ])>
|
||||
<span class="f16" data-bs-toggle="tooltip" data-bs-custom-class="custom-tooltip" aria-label="{{ $langtag }}" data-bs-original-title="{{ ($x=preg_replace('/'.Entry::LANG_TAG_PREFIX.'/','',$langtag)) }}"><i class="flag {{ $x }}"></i></span>
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-attribute :o="$o" :edit="$edit" :new="$new" :langtag="$langtag" :updated="$updated"/>
|
||||
@switch($template?->attributeType($o->name))
|
||||
@case('select')
|
||||
<x-attribute.template.select :o="$o" :template="$template" :edit="(! $template?->attributeReadOnly($o->name)) && $edit" :new="$new"/>
|
||||
@break;
|
||||
|
||||
@default
|
||||
<x-attribute :o="$o" :edit="(! $template?->attributeReadOnly($o->name)) && $edit" :new="$new" :updated="$updated"/>
|
||||
@endswitch
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,21 +1,60 @@
|
||||
<!-- $o=Attribute::class -->
|
||||
<x-attribute.layout :edit="$edit=($edit ?? FALSE)" :new="$new=($new ?? FALSE)" :o="$o">
|
||||
<x-attribute.layout :edit="$edit=($edit ?? FALSE)" :new="$new=($new ?? FALSE)" :o="$o" :template="$template">
|
||||
<div class="col-12">
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$new ? [NULL] : $o->tagValues($langtag)]),$langtag,[]) as $key => $value)
|
||||
@if($edit && (! $o->is_rdn))
|
||||
<div class="input-group has-validation">
|
||||
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! ($tv=$o->tagValuesOld($langtag))->contains($value),'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)
|
||||
{{ join('|',$e) }}
|
||||
<div class="tab-content">
|
||||
@foreach($o->langtags as $langtag)
|
||||
<span @class(['tab-pane','active'=>$loop->index === 0]) id="langtag-{{ $o->name_lc }}-{{ $langtag }}" role="tabpanel">
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$new ? [NULL] : $o->tagValues($langtag)]),$langtag,[]) as $key => $value)
|
||||
<!-- AutoValue Lock -->
|
||||
@if($new && $template && ($av=$template->attributeValue($o->name_lc)))
|
||||
<input type="hidden" name="_auto_value[{{ $o->name_lc }}]" value="{{ $av }}">
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
<input type="text" @class(['form-control','mb-1','bg-success-subtle'=>$updated]) value="{{ $value }}" disabled>
|
||||
<div class="input-group has-validation">
|
||||
<input type="text"
|
||||
@class([
|
||||
'form-control',
|
||||
'noedit'=>(! $edit) || ($o->is_rdn),
|
||||
'is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)) || ($e=$errors->get('_auto_value.'.$o->name_lc)),
|
||||
'mb-1',
|
||||
'border-focus'=>! ($tv=$o->tagValuesOld($langtag))->contains($value),
|
||||
'bg-success-subtle'=>$updated])
|
||||
name="{{ $o->name_lc }}[{{ $langtag }}][]"
|
||||
value="{{ $value ?: ($av ?? '') }}"
|
||||
placeholder="{{ ! is_null($x=$tv->get($loop->index)) ? $x : '['.__('NEW').']' }}"
|
||||
@readonly(! $edit)
|
||||
@disabled($o->isDynamic())>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e)
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</span>
|
||||
@endforeach
|
||||
|
||||
@if($edit && (! $o->is_rdn))
|
||||
<span @class(['tab-pane']) id="langtag-{{ $o->name_lc }}-+" role="tabpanel">
|
||||
<span class="d-flex font-size-sm alert alert-warning p-2">
|
||||
It is not possible to create new language tags at the moment. This functionality should come soon.<br>
|
||||
You can create them with an LDIF import though.
|
||||
</span>
|
||||
</span>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</x-attribute.layout>
|
||||
|
||||
@if($new && ($x=$template?->onChange($o->name))?->count())
|
||||
@section('page-scripts')
|
||||
<!-- START: ONCHANGE PROCESSING {{ $o->name }} -->
|
||||
<script type="text/javascript">
|
||||
$('#{{ $o->name_lc }}').on('change',function() {
|
||||
{!! $x->join('') !!}
|
||||
});
|
||||
</script>
|
||||
<!-- END: ONCHANGE PROCESSING {{ $o->name }} -->
|
||||
@append
|
||||
@endif
|
@ -1,3 +1,5 @@
|
||||
@use(App\Ldap\Entry)
|
||||
|
||||
<!-- @todo We are not handling redirect backs yet with updated photos -->
|
||||
<!-- $o=Binary\JpegPhoto::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@ -8,8 +10,8 @@
|
||||
@case('image/jpeg')
|
||||
@default
|
||||
<td>
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ md5($value) }}">
|
||||
<img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'bg-success-subtle'=>$updated]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ Entry::TAG_NOTAG }}][]" value="{{ md5($value) }}">
|
||||
<img alt="{{ $o->dn }}" @class(['border','rounded','p-2','m-0','is-invalid'=>($e=$errors->get($o->name_lc.'.'.Entry::TAG_NOTAG.'.'.$loop->index)),'bg-success-subtle'=>$updated]) src="data:{{ $x }};base64, {{ base64_encode($value) }}" />
|
||||
|
||||
@if($edit)
|
||||
<br>
|
||||
|
@ -1,18 +1,20 @@
|
||||
<!-- $o=KrbPrincipleKey::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) 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="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) 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="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e)
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e)
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -1,22 +1,24 @@
|
||||
<!-- $o=KrbTicketFlags::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<div id="32"></div>
|
||||
<div id="16"></div>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<div id="32"></div>
|
||||
<div id="16"></div>
|
||||
|
||||
<div class="input-group has-validation mb-3">
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" @readonly(true)>
|
||||
<div class="input-group has-validation mb-3">
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}" @readonly(true)>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
||||
|
||||
@ -48,7 +50,7 @@
|
||||
$('div#32').append(binary(31,16));
|
||||
$('div#16').append(binary(15,0));
|
||||
|
||||
$('attribute#krbTicketFlags').find('i')
|
||||
$('attribute#krbticketflags').find('i')
|
||||
.on('click',function() {
|
||||
var item = $(this);
|
||||
if ($('form#dn-edit').attr('readonly'))
|
||||
@ -91,7 +93,7 @@
|
||||
item.data('old',null);
|
||||
}
|
||||
|
||||
$('attribute#krbTicketFlags').find('input').val(value);
|
||||
$('attribute#krbticketflags').find('input').val(value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -102,7 +104,7 @@
|
||||
} else {
|
||||
krbticketflags();
|
||||
|
||||
$('attribute#krbTicketFlags').find('i')
|
||||
$('attribute#krbticketflags').find('i')
|
||||
.tooltip();
|
||||
}
|
||||
</script>
|
||||
|
@ -1,11 +1,10 @@
|
||||
<div class="row pt-2">
|
||||
<div @class(['col-1','d-none'=>(! $edit) && (! ($detail ?? FALSE))])></div>
|
||||
<div class="col-10">
|
||||
<attribute id="{{ $o->name }}">
|
||||
<div class="col-10 offset-1">
|
||||
<attribute id="{{ $o->name_lc }}">
|
||||
{{ $slot }}
|
||||
</attribute>
|
||||
|
||||
<x-attribute.widget.options :o="$o" :edit="$edit" :new="$new"/>
|
||||
<x-attribute.widget.options :o="$o" :edit="$edit" :new="$new" :template="$template ?? FALSE"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,15 +1,17 @@
|
||||
<!-- $o=Attribute/ObjectClass::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<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))
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}">
|
||||
<span class="float-end">@lang('structural')</span>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<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))
|
||||
<input type="hidden" name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ $value }}">
|
||||
<span class="float-end">@lang('structural')</span>
|
||||
@endif
|
||||
<br>
|
||||
@endif
|
||||
<br>
|
||||
@endif
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -1,28 +1,30 @@
|
||||
<!-- $o=Password::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<div class="input-group has-validation mb-3">
|
||||
<x-form.select id="userpassword_hash_{{$loop->index}}" name="userpassword_hash[{{ $langtag }}][]" :value="$o->hash($new ? '' : $value)->id()" :options="$helpers" allowclear="false" :disabled="! $new"/>
|
||||
<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="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<div class="input-group has-validation">
|
||||
<x-form.select id="userpassword_hash_{{$loop->index}}{{$template?->name ?: ''}}" name="_userpassword_hash[{{ $langtag }}][]" :value="old('_userpassword_hash.'.$langtag.'.0',$o->hash($new ? '' : ($value ?? ''))->id())" :options="$helpers" allowclear="false" :disabled="! $new"/>
|
||||
<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="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e)
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e)
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
||||
|
||||
@if($edit)
|
||||
@if($edit && $o->tagValuesOld($langtag)->dot()->filter()->count())
|
||||
<div class="row">
|
||||
<div class="offset-1 col-4 p-2">
|
||||
<div class="offset-1 col-4">
|
||||
<span class="p-0 m-0">
|
||||
<button id="entry-userpassword-check" type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
|
||||
<button name="entry-userpassword-check" type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,24 +3,24 @@
|
||||
@foreach(($o->values->count() ? $o->values : [NULL]) as $value)
|
||||
@if($edit)
|
||||
<div class="input-group has-validation mb-3">
|
||||
<select class="form-select @error('rdn')is-invalid @enderror" id="rdn" name="rdn">
|
||||
<select @class(['form-select','is-invalid'=>$errors->get('_rdn')]) id="rdn" name="_rdn">
|
||||
<option value=""></option>
|
||||
|
||||
@foreach($o->attrs->map(fn($item)=>['id'=>$item,'value'=>$item]) as $option)
|
||||
@continue(! Arr::get($option,'value'))
|
||||
<option value="{{ Arr::get($option,'id') }}" @selected(Arr::get($option,'id') == old('rdn',$value ?? ''))>{{ Arr::get($option,'value') }}</option>
|
||||
<option value="{{ strtolower(Arr::get($option,'id')) }}" @selected(Arr::get($option,'id') == old('_rdn',$value ?? ''))>{{ Arr::get($option,'value') }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<span class="input-group-text">=</span>
|
||||
<input type="text" @class(['form-control','is-invalid'=>$errors->get('rdn_value')]) id="rdn_value" name="rdn_value" value="{{ old('rdn_value') }}" placeholder="rdn">
|
||||
<input type="text" @class(['form-control','is-invalid'=>$errors->get('_rdn_value')]) id="rdn_value" name="_rdn_value" value="{{ old('_rdn_value') }}" placeholder="rdn">
|
||||
<label class="input-group-text" for="inputGroupSelect02">,{{ $o->base }}</label>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@error('rdn')
|
||||
@error('_rdn')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
@error('rdn_value')
|
||||
@error('_rdn_value')
|
||||
{{ $message }}
|
||||
@enderror
|
||||
</div>
|
||||
|
@ -0,0 +1,28 @@
|
||||
<!-- $o=Attribute::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||
@if($edit)
|
||||
<div class="select-group">
|
||||
<x-form.select
|
||||
@class(['is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)])
|
||||
id="{{ $o->name_lc }}_{{$loop->index}}{{$template?->name ?: ''}}"
|
||||
name="{{ $o->name_lc }}[{{ $langtag }}][]"
|
||||
:value="$value"
|
||||
:options="$template->attributeOptions($o->name_lc)"
|
||||
allowclear="true"
|
||||
:disabled="! $new"
|
||||
:readonly="false"/>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||
@endif
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -2,7 +2,7 @@
|
||||
<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)>
|
||||
@if ($o->isStructural($value))
|
||||
@if($o->isStructural($value))
|
||||
<span class="input-group-end text-black-50">@lang('structural')</span>
|
||||
@else
|
||||
<span class="input-group-end"><i class="fas fa-fw fa-xmark"></i></span>
|
||||
|
@ -6,16 +6,16 @@
|
||||
@php($clone=FALSE)
|
||||
<span class="p-0 m-0">
|
||||
@if($o->is_rdn)
|
||||
<button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button>
|
||||
<span id="entry-rename" class="btn btn-sm btn-outline-focus mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
|
||||
@elseif($edit && $o->can_addvalues)
|
||||
@switch(get_class($o))
|
||||
@case(Certificate::class)
|
||||
@case(CertificateList::class)
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-replace" disabled><i class="fas fa-fw fa-certificate"></i> @lang('Replace')</span>
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}-replace" disabled><i class="fas fa-fw fa-certificate"></i> @lang('Replace')</span>
|
||||
@section('page-scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#{{ $o->name }}-replace.addable').click(function(e) {
|
||||
$('attribute#{{ $o->name_lc }}-replace.addable').click(function(e) {
|
||||
alert('Sorry, not implemented yet');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
@ -26,7 +26,7 @@
|
||||
@break
|
||||
|
||||
@case(ObjectClass::class)
|
||||
<span type="button" @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</span>
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</span>
|
||||
|
||||
<!-- NEW OBJECT CLASS -->
|
||||
<div class="modal fade" id="new_objectclass-modal" tabindex="-1" aria-labelledby="new_objectclass-label" aria-hidden="true" data-bs-backdrop="static">
|
||||
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<x-form.select id="newoc" label="Select from..."/>
|
||||
<x-form.select id="newoc" :label="__('Select from').'...'"/>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
@ -55,7 +55,7 @@
|
||||
var rendered = false;
|
||||
var newadded = [];
|
||||
|
||||
var oc = $('attribute#objectClass input[type=text]')
|
||||
var oc = $('attribute#objectclass input[type=text]')
|
||||
.map((key,item)=>{return $(item).val()}).toArray();
|
||||
|
||||
if (newadded.length)
|
||||
@ -81,7 +81,7 @@
|
||||
},
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
$('#{{ $o->name }}').append(data);
|
||||
$('attribute#{{ $o->name_lc }}').append(data);
|
||||
},
|
||||
error: function(e) {
|
||||
if (e.status !== 412)
|
||||
@ -98,13 +98,13 @@
|
||||
// Render any must attributes
|
||||
if (data.must.length) {
|
||||
data.must.forEach(function(item) {
|
||||
if ($('attribute#'+item).length)
|
||||
if ($('attribute#'+item.toLowerCase()).length)
|
||||
return;
|
||||
|
||||
// Add attribute to the page
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '{{ url('entry/attr/add') }}/'+item,
|
||||
url: '{{ url('entry/attr/add') }}/'+item.toLowerCase(),
|
||||
data: {
|
||||
value: item,
|
||||
objectclasses: oc,
|
||||
@ -237,11 +237,11 @@
|
||||
@break
|
||||
|
||||
@case(JpegPhoto::class)
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-upload" disabled><i class="fas fa-fw fa-file-arrow-up"></i> @lang('Upload JpegPhoto')</span>
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}-upload" disabled><i class="fas fa-fw fa-file-arrow-up"></i> @lang('Upload JpegPhoto')</span>
|
||||
@section('page-scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#{{ $o->name }}-upload.addable').click(function(e) {
|
||||
$('#{{ $o->name_lc }}-upload.addable').click(function(e) {
|
||||
alert('Sorry, not implemented yet');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
@ -255,22 +255,25 @@
|
||||
@default
|
||||
@if($o->isDynamic()) @break @endif
|
||||
@php($clone=TRUE)
|
||||
@if($o->values_old->count())
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
|
||||
@if($o->values_old->count() && (! $template))
|
||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-attribute="{{ $o->name_lc }}" id="{{ $o->name_lc }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
|
||||
@endif
|
||||
|
||||
@section('page-scripts')
|
||||
@if($clone && $edit && $o->can_addvalues)
|
||||
@if((! $template) && $clone && $edit && $o->can_addvalues)
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
// Create a new entry when Add Value clicked
|
||||
$('#{{ $o->name }}-addnew.addable').click(function (item) {
|
||||
var cln = $(this).parent().parent().find('input:last').parent().clone();
|
||||
cln.find('input:last')
|
||||
$('form#dn-edit #{{ $o->name_lc }}-addnew.addable').click(function(item) {
|
||||
var attribute = $(this).data('attribute');
|
||||
var active = $('#template-default attribute[id='+attribute+']');
|
||||
|
||||
active.find('input:last')
|
||||
.clone()
|
||||
.attr('value','')
|
||||
.attr('placeholder', '[@lang('NEW')]')
|
||||
.attr('placeholder','[@lang('NEW')]')
|
||||
.addClass('border-focus')
|
||||
.appendTo('#'+item.currentTarget.id.replace('-addnew',''));
|
||||
.appendTo(active);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,8 +1,10 @@
|
||||
<!-- $o=Attribute::class -->
|
||||
<x-attribute.layout :edit="false" :new="false" :o="$o" :detail="true">
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control mb-1" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" disabled>
|
||||
</div>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control mb-1" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" disabled>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -1,8 +1,10 @@
|
||||
<!-- $o=Attribute::class -->
|
||||
<x-attribute.layout :edit="false" :new="false" :o="$o" :detail="true">
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
|
||||
</div>
|
||||
@foreach($o->langtags as $langtag)
|
||||
@foreach(Arr::get(old($o->name_lc,[$langtag=>$o->tagValues($langtag)]),$langtag,[]) as $value)
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control mb-1" value="{{ $value }}" disabled>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -1,4 +1,4 @@
|
||||
<button id="form-reset" class="btn btn-outline-danger">@lang('Reset')</button>
|
||||
<button id="form-reset" class="btn btn-sm btn-outline-danger">@lang('Reset')</button>
|
||||
|
||||
@section('page-scripts')
|
||||
<script>
|
||||
|
@ -2,27 +2,28 @@
|
||||
@isset($name)
|
||||
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
|
||||
@endisset
|
||||
<select class="form-select @isset($name)@error((! empty($old)) ? $old : ($id ?? $name)) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
|
||||
@if((empty($value) && ! empty($options)) || isset($addnew) || isset($choose))
|
||||
|
||||
<select class="form-select @error($old ?? $id ?? $name) is-invalid @enderror" id="{{ $id ?? $name}}" @isset($name)name="{{ $name }}"@endisset @required($required ?? FALSE) @disabled($disabled ?? FALSE)>
|
||||
@if((empty($value) && ! empty($options)) || isset($addnew))
|
||||
<option value=""></option>
|
||||
|
||||
@isset($addnew)
|
||||
<option value="new">{{ $addnew ?: 'Add New' }}</option>
|
||||
@endisset
|
||||
@endif
|
||||
|
||||
@isset($options)
|
||||
@empty($groupby)
|
||||
@foreach($options as $option)
|
||||
@continue(! Arr::get($option,'value'))
|
||||
<option value="{{ Arr::get($option,'id') }}" @selected(isset($name) && (Arr::get($option,'id') == old($old ?? $name,$value ?? '')))>{{ Arr::get($option,'value') }}</option>
|
||||
<option value="{{ Arr::get($option,'id') }}" @selected(Arr::get($option,'id') == collect(old())->dot()->get(isset($old) ? $old.'.0' : ($id ?? $name),$value ?? ''))>{{ Arr::get($option,'value') }}</option>
|
||||
@endforeach
|
||||
|
||||
@else
|
||||
@foreach($options->groupBy($groupby) as $group)
|
||||
<optgroup label="{{ $groupby == 'active' ? (Arr::get($group->first(),$groupby) ? 'Active' : 'Not Active') : Arr::get($group->first(),$groupby) }}">
|
||||
<optgroup label="{{ Arr::get($group->first(),$groupby) }}">
|
||||
@foreach($group as $option)
|
||||
@continue(! Arr::get($option,'value'))
|
||||
<option value="{{ Arr::get($option,'id') }}" @selected(isset($name) && (Arr::get($option,'id') == old($old ?? $name,$value ?? '')))>{{ Arr::get($option,'value') }}</option>
|
||||
<option value="{{ Arr::get($option,'id') }}" @selected(Arr::get($option,'id') == collect(old())->dot()->get(isset($old) ? $old.'.0' : ($id ?? $name),$value ?? ''))>{{ Arr::get($option,'value') }}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
|
@ -1,11 +1,5 @@
|
||||
@if(session()->has('success'))
|
||||
<div class="alert alert-success">
|
||||
<h4 class="alert-heading"><i class="fas fa-fw fa-thumbs-up"></i> Success!</h4>
|
||||
<hr>
|
||||
<ul style="list-style-type: square;">
|
||||
@foreach (session()->get('success') as $item)
|
||||
<li>{{ $item }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<div class="alert alert-success p-2">
|
||||
<p class="m-0"><i class="fas fa-fw fa-thumbs-up"></i> {{ session()->pull('success') }}</p>
|
||||
</div>
|
||||
@endif
|
@ -3,22 +3,42 @@
|
||||
<!-- $o=Certificate::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach($o->tagValuesOld('binary') as $key => $value)
|
||||
<!-- If this attribute is not handle, it'll be an Attribute::class, we'll just render it normally -->
|
||||
<!-- If this attribute is not handled, it'll be an Attribute::class, we'll just render it normally -->
|
||||
@if(($o instanceof Certificate) && $edit)
|
||||
<input type="hidden" name="name={{ $o->name_lc }}[binary][]" value="{{ md5($value) }}">
|
||||
|
||||
<div class="input-group has-validation mb-3">
|
||||
<textarea class="form-control mb-1 font-monospace" rows="{{ count(explode("\n",$x=$o->certificate())) }}" style="overflow: hidden; font-size: 90%;" disabled>{{ $x }}</textarea>
|
||||
<textarea class="form-control mb-1 font-size-md font-monospace overflow-hidden" rows="{{ count(explode("\n",$x=$o->certificate())) }}" disabled>{{ $x }}</textarea>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
|
||||
@if($e=$errors->get($o->name_lc.'.binary.'.$loop->index))
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-helper">
|
||||
@lang('Certificate Subject'): <strong>{{ $o->subject($loop->index) }}</strong><br/>
|
||||
{{ ($expire=$o->expires($loop->index))->isPast() ? __('Expired') : __('Expires') }}: <strong>{{ $expire->format(config('pla.datetime_format','Y-m-d H:i:s')) }}</strong>
|
||||
<div class="input-helper small">
|
||||
<table class="table table-borderless w-75">
|
||||
<tr >
|
||||
<td class="p-0">@lang('Certificate Subject')</td>
|
||||
<th class="p-0">{{ $o->subject($loop->index) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0">{{ ($expire=$o->expires($loop->index))->isPast() ? __('Expired') : __('Expires') }}</td>
|
||||
<th class="p-0">{{ $expire->format(config('pla.datetime_format','Y-m-d H:i:s')) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0">@lang('Serial Number')</td>
|
||||
<th class="p-0">{{ $o->cert_info('serialNumberHex',$loop->index) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0">@lang('Subject Key Identifier')</td>
|
||||
<th class="p-0">{{ $o->subject_key_identifier($loop->index) }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0">@lang('Authority Key Identifier')</td>
|
||||
<th class="p-0">{{ $o->authority_key_identifier($loop->index) }}</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@else
|
||||
|
@ -1,7 +1,14 @@
|
||||
<!-- $o=CertificateList::class -->
|
||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||
@foreach($o->tagValuesOld('binary') as $key => $value)
|
||||
<!-- If this attribute is not handle, it'll be an Attribute::class, we'll just render it normally -->
|
||||
<span class="form-control mb-1"><pre class="m-0">{{ $o->render_item_old('binary.'.$key) }}</pre></span>
|
||||
<div class="input-group has-validation mb-3">
|
||||
<textarea class="form-control mb-1 font-size-md font-monospace overflow-hidden" rows="{{ count(explode("\n",$x=$o->render_item_old('binary.'.$key))) }}" disabled>{{ $x }}</textarea>
|
||||
|
||||
<div class="invalid-feedback pb-2">
|
||||
@if($e=$errors->get($o->name_lc.'.binary.'.$loop->index))
|
||||
{{ join('|',$e) }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</x-attribute.layout>
|
@ -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() as $item)
|
||||
<tr>
|
||||
<td class="ps-0">{{ $item->getDn() }}</td>
|
||||
</tr>
|
||||
@ -44,6 +44,12 @@
|
||||
<td>Root URL</td>
|
||||
<td>{{ request()->root() }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Locale -->
|
||||
<tr>
|
||||
<td>Locale</td>
|
||||
<td>{{ __('locale') }} [{{ config('app.locale') }}]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
33
resources/views/errors/501.blade.php
Normal file
33
resources/views/errors/501.blade.php
Normal file
@ -0,0 +1,33 @@
|
||||
@extends('architect::layouts.error')
|
||||
|
||||
@section('error')
|
||||
501: @lang('LDAP User Error')
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<table class="table table-sm table-borderless table-condensed">
|
||||
<tr>
|
||||
<th>@lang('Error')</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">{{ $exception->getMessage() }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>@lang('Possible Causes')</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<ul class="ps-3">
|
||||
<li>The DN you used to login actually doesnt exist in the server (DN's must exist in order to login)</li>
|
||||
<li>You are attempting to use the <strong>rootdn</strong> to login (not supported)</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>To suppress this message, set <strong>LDAP_ALERT_ROOTDN</strong> to <strong>FALSE</strong> before starting PLA.</p>
|
||||
<p>Back to <a href="{{ url('login') }}">login</a>?</p>
|
||||
|
||||
@endsection
|
@ -1,19 +1,20 @@
|
||||
<div id="newattrs"></div>
|
||||
|
||||
<hr class="opacity-05">
|
||||
|
||||
<!-- Add new attributes -->
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-1 col-md-2"></div>
|
||||
<div class="col-12 col-sm-10 col-md-8">
|
||||
<div class="d-none" id="newattr-select">
|
||||
<div class="col-12 offset-lg-1 col-lg-10">
|
||||
<div class="d-none round" id="newattr-select">
|
||||
<div class="row">
|
||||
<div class="col-12 bg-dark text-light p-2">
|
||||
<i class="fas fa-plus-circle"></i> Add New Attribute
|
||||
<div class="col-12 bg-dark text-light p-2 rounded-2">
|
||||
<i class="fas fa-plus-circle"></i> @lang('Add New Attribute')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 pt-2">
|
||||
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->unique('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"/>
|
||||
<x-form.select id="newattr" :label="__('Select from').'...'" :options="$o->getMissingAttributes()->sortBy('name')->unique('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,5 @@
|
||||
@use(App\Ldap\Entry)
|
||||
|
||||
<table class="table table-borderless">
|
||||
<tr class="border-bottom line-height-2">
|
||||
<td class="p-1 pt-0" rowspan="2">
|
||||
@ -6,7 +8,7 @@
|
||||
<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">
|
||||
<td class="align-bottom font-size-xs" colspan="2">
|
||||
<table class="table table-condensed table-borderless w-100">
|
||||
<tr class="mt-1">
|
||||
<td class="p-0 pe-2">Created</td>
|
||||
@ -26,10 +28,17 @@
|
||||
<x-attribute :o="$o->getObject('entryuuid')"/>
|
||||
</th>
|
||||
</tr>
|
||||
@if($langtags->count())
|
||||
<!-- It is assumed that langtags contains at least Entry::TAG_NOTAG -->
|
||||
@if(($x=$o->getLangTags()
|
||||
->flatMap(fn($item)=>$item->values())
|
||||
->unique()
|
||||
->sort()
|
||||
->filter(fn($item)=>($item !== Entry::TAG_NOTAG))
|
||||
->map(fn($item)=>preg_replace('/'.Entry::LANG_TAG_PREFIX.'/','',$item)))
|
||||
->count())
|
||||
<tr class="mt-1">
|
||||
<td class="p-0 pe-2">Tags</td>
|
||||
<th class="p-0">{{ $langtags->join(', ') }}</th>
|
||||
<th class="p-0">{{ $x->join(', ') }}</th>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
|
@ -2,19 +2,19 @@
|
||||
<div class="col-12 col-xl-3">
|
||||
<select id="attributetype" class="form-control">
|
||||
<option value="-all-">-all-</option>
|
||||
@foreach ($attributetypes as $o)
|
||||
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
|
||||
@foreach(($at=$attributetypes->sortBy(fn($item)=>$item->names_lc->join(','))) as $o)
|
||||
<option value="{{ $o->names_lc->join('-') }}">{{ $o->names->join(',') }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-9">
|
||||
@foreach ($attributetypes as $o)
|
||||
<span id="at-{{ $o->name_lc }}">
|
||||
@foreach($at as $o)
|
||||
<span id="at-{{ $o->names_lc->join('-') }}">
|
||||
<table class="schema table table-sm table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-dark" colspan="2">{{ $o->name }}<span class="float-end"><abbr title="{{ $o->line }}"><i class="fas fa-fw fa-file-contract"></i></abbr></span></th>
|
||||
<th class="table-dark" colspan="2">{{ $o->names->join(' / ') }}<span class="float-end"><abbr title="{{ $o->line }}"><i class="fas fa-fw fa-file-contract"></i></abbr></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -30,16 +30,16 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@lang('Inherits from')</td>
|
||||
<td><strong>@if ($o->sup_attribute)<a class="attributetype" id="{{ strtolower($o->sup_attribute) }}" href="#{{ strtolower($o->sup_attribute) }}">{{ $o->sup_attribute }}</a>@else @lang('(none)')@endif</strong></td>
|
||||
<td><strong>@if($o->sup_attribute)<a class="attributetype" id="{{ strtolower($o->sup_attribute) }}" href="#{{ strtolower($o->sup_attribute) }}">{{ $o->sup_attribute }}</a>@else @lang('(none)')@endif</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@lang('Parent to')</td>
|
||||
<td>
|
||||
<strong>
|
||||
@if (! $o->children->count())
|
||||
@if(! $o->children->count())
|
||||
@lang('(none)')
|
||||
@else
|
||||
@foreach ($o->children->sort() as $child)
|
||||
@foreach($o->children->sort() as $child)
|
||||
@if($loop->index)</strong> <strong>@endif
|
||||
<a class="attributetype" id="{{ strtolower($child) }}" href="#{{ strtolower($child) }}">{{ $child }}</a>
|
||||
@endforeach
|
||||
@ -57,7 +57,7 @@
|
||||
<td>@lang('Substring Rule')</td><td><strong>{{ $o->sub_str_rule ?: __('(not specified)') }}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@lang('Syntax')</td><td><strong>{{ ($o->syntax_oid && $x=$server->schemaSyntaxName($o->syntax_oid)) ? $x->description : __('(unknown syntax)') }} @if($o->syntax_oid)({{ $o->syntax_oid }})@endif</strong></td>
|
||||
<td>@lang('Syntax')</td><td><strong>{{ ($o->syntax_oid && $x=$server->get_syntax($o->syntax_oid)) ? $x->description : __('(unknown syntax)') }} @if($o->syntax_oid)({{ $o->syntax_oid }})@endif</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@lang('Single Valued')</td><td><strong>@lang($o->is_single_value ? 'Yes' : 'No')</strong></td>
|
||||
@ -77,11 +77,8 @@
|
||||
<tr>
|
||||
<td>@lang('Aliases')</td>
|
||||
<td><strong>
|
||||
@if ($o->aliases->count())
|
||||
@foreach ($o->aliases as $alias)
|
||||
@if ($loop->index)</strong> <strong>@endif
|
||||
<a class="attributetype" id="{{ strtolower($alias) }}" href="#{{ strtolower($alias) }}">{{ $alias }}</a>
|
||||
@endforeach
|
||||
@if($o->names->count() > 1)
|
||||
{!! $o->names->join('</strong>, <strong>') !!}
|
||||
@else
|
||||
@lang('(none)')
|
||||
@endif
|
||||
@ -90,8 +87,8 @@
|
||||
<tr>
|
||||
<td>@lang('Used by ObjectClasses')</td>
|
||||
<td>
|
||||
@if ($o->used_in_object_classes->count())
|
||||
@foreach ($o->used_in_object_classes as $class => $structural)
|
||||
@if($o->used_in_object_classes->count())
|
||||
@foreach($o->used_in_object_classes as $class => $structural)
|
||||
@if($structural)
|
||||
<strong>
|
||||
@endif
|
||||
@ -108,8 +105,8 @@
|
||||
<tr>
|
||||
<td>@lang('Required by ObjectClasses')</td>
|
||||
<td>
|
||||
@if ($o->required_by_object_classes->count())
|
||||
@foreach ($o->required_by_object_classes as $class => $structural)
|
||||
@if($o->required_by_object_classes->count())
|
||||
@foreach($o->required_by_object_classes as $class => $structural)
|
||||
@if($structural)
|
||||
<strong>
|
||||
@endif
|
||||
|
@ -9,14 +9,14 @@
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach ($ldapsyntaxes as $o)
|
||||
@foreach($ldapsyntaxes as $o)
|
||||
<tr>
|
||||
<td>
|
||||
<abbr title="{{ $o->line }}">{{ $o->description }}</abbr>
|
||||
@if ($o->binary_transfer_required)
|
||||
@if($o->binary_transfer_required)
|
||||
<span class="float-end"><i class="fas fa-fw fa-file-download"></i></span>
|
||||
@endif
|
||||
@if ($o->is_not_human_readable)
|
||||
@if($o->is_not_human_readable)
|
||||
<span class="float-end"><i class="fas fa-fw fa-tools"></i></span>
|
||||
@endif
|
||||
</td>
|
||||
|
@ -2,14 +2,14 @@
|
||||
<div class="col-12 col-xl-3">
|
||||
<select id="matchingrule" class="form-control">
|
||||
<option value="-all-">-all-</option>
|
||||
@foreach ($matchingrules as $o)
|
||||
@foreach($matchingrules as $o)
|
||||
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-9">
|
||||
@foreach ($matchingrules as $o)
|
||||
@foreach($matchingrules as $o)
|
||||
<span id="me-{{ $o->name_lc }}">
|
||||
<table class="schema table table-sm table-bordered table-striped">
|
||||
<thead>
|
||||
@ -32,10 +32,10 @@
|
||||
<td>@lang('Used by Attributes')</td>
|
||||
<td>
|
||||
<strong>
|
||||
@if ($o->used_by_attrs->count() === 0)
|
||||
@if($o->used_by_attrs->count() === 0)
|
||||
@lang('(none)')
|
||||
@else
|
||||
@foreach ($o->used_by_attrs as $attr)
|
||||
@foreach($o->used_by_attrs as $attr)
|
||||
@if($loop->index)</strong> <strong>@endif
|
||||
<a class="attributetype" id="{{ strtolower($attr) }}" href="#at-{{ strtolower($attr) }}">{{ $attr }}</a>
|
||||
@endforeach
|
||||
|
@ -35,10 +35,10 @@
|
||||
<td>@lang('Inherits from')</td>
|
||||
<td colspan="3">
|
||||
<strong>
|
||||
@if($o->sup->count() === 0)
|
||||
@if($o->sup_classes->count() === 0)
|
||||
@lang('(none)')
|
||||
@else
|
||||
@foreach($o->sup as $sup)
|
||||
@foreach($o->sup_classes as $sup)
|
||||
@if($loop->index)</strong> <strong>@endif
|
||||
<a class="objectclass" id="{{ strtolower($sup) }}" href="#{{ strtolower($sup) }}">{{ $sup }}</a>
|
||||
@endforeach
|
||||
@ -53,10 +53,10 @@
|
||||
<strong>
|
||||
@if(strtolower($o->name) === 'top')
|
||||
<a class="objectclass" id="-all-">(all)</a>
|
||||
@elseif(! $o->getChildObjectClasses()->count())
|
||||
@elseif(! $o->child_classes->count())
|
||||
@lang('(none)')
|
||||
@else
|
||||
@foreach($o->getChildObjectClasses() as $childoc)
|
||||
@foreach($o->child_classes as $childoc)
|
||||
@if($loop->index)</strong> <strong>@endif
|
||||
<a class="objectclass" id="{{ strtolower($childoc) }}" href="#{{ strtolower($childoc) }}">{{ $childoc }}</a>
|
||||
@endforeach
|
||||
@ -77,7 +77,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<ul class="ps-3" style="list-style-type: square;">
|
||||
<ul class="ps-3 square">
|
||||
@foreach($o->getMustAttrs(TRUE) as $oo)
|
||||
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
|
||||
@endforeach
|
||||
@ -99,7 +99,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<ul class="ps-3" style="list-style-type: square;">
|
||||
<ul class="ps-3 square">
|
||||
@foreach($o->getMayAttrs(TRUE) as $oo)
|
||||
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
|
||||
@endforeach
|
||||
|
16
resources/views/fragment/template/dn.blade.php
Normal file
16
resources/views/fragment/template/dn.blade.php
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- $template=Template -->
|
||||
<form id="template-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate readonly>
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="dn" value="">
|
||||
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
@php($up=(session()->get('updated') ?: collect()))
|
||||
|
||||
@foreach($o->getVisibleAttributes()->filter(fn($item)=>$template->attributes->keys()->map('strtolower')->contains($item->name_lc)) as $ao)
|
||||
<x-attribute-type :o="$ao" :edit="true" :new="false" :template="$template" :updated="$up->contains($ao->name)"/>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -3,9 +3,3 @@
|
||||
@section('main-content')
|
||||
@include('frames.'.$subframe)
|
||||
@endsection
|
||||
|
||||
@section('page-scripts')
|
||||
<script type="text/javascript">
|
||||
var basedn = {!! $bases->toJson() !!};
|
||||
</script>
|
||||
@append
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user