Compare commits

..

No commits in common. "9798863e349d556394bf9222da06c23bcce0717e" and "b08de519d4091fe5f89192b6e7fa145f2167ae6a" have entirely different histories.

101 changed files with 2202 additions and 2908 deletions

View File

@ -2,6 +2,7 @@ APP_NAME=Laravel
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
LOG_CHANNEL=daily
@ -11,7 +12,7 @@ SESSION_DRIVER=file
SESSION_LIFETIME=120
LDAP_HOST=
LDAP_BASE_DN=
LDAP_USERNAME=
LDAP_PASSWORD=
LDAP_CACHE=false
LDAP_ALERT_ROOTDN=true

View File

@ -2,6 +2,7 @@ APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stderr
@ -11,6 +12,7 @@ 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

View File

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

View File

@ -1,10 +1,4 @@
# phpLDAPadmin
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/leenooks/phpldapadmin)
![Docker Pulls](https://img.shields.io/docker/pulls/phpldapadmin/phpldapadmin)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/leenooks/phpldapadmin/total)
![GitHub Release Date](https://img.shields.io/github/release-date/leenooks/phpldapadmin)
![GitHub commits since latest release](https://img.shields.io/github/commits-since/leenooks/phpldapadmin/latest)
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.
@ -33,29 +27,38 @@ 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.
## 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.
## Version 2 Progress
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.
The update to v2 is progressing well - here is a list of work to do and done:
## Outstanding items
Compare to v1.x, there are a couple of outstanding items to address
Entry Editing:
- [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
- [ ] JpegPhoto Create/Delete
- [ ] Binary attribute upload
- [ ] If removing an objectClass, remove all attributes that only that objectclass provided
- [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?
Templates Engine
- [ ] Present SELECT lists when an attribute is marked as `type=select`
- [ ] 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.
## Support is known for these LDAP servers:
Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory

View File

@ -7,8 +7,6 @@ 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;
/**
@ -16,6 +14,9 @@ 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;
@ -97,11 +98,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);
@ -112,12 +113,8 @@ class Attribute implements \Countable, \ArrayAccess
// Get the objectclass heirarchy for required attribute determination
foreach ($oc as $objectclass) {
$soc = config('server')->schema('objectclasses',$objectclass);
if ($soc) {
$this->oc->push($soc->oid);
$this->oc = $this->oc->merge($soc->getParents()->pluck('oid'));
}
$this->oc->push($objectclass);
$this->oc = $this->oc->merge(config('server')->schema('objectclasses',$objectclass)->getParents()->pluck('name'));
}
/*
@ -143,6 +140,8 @@ 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
@ -165,7 +164,7 @@ 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->{$key},
'name' => $this->schema ? $this->schema->{$key} : $this->{$key},
// Attribute name in lower case
'name_lc' => strtolower($this->name),
// Required by Object Classes
@ -269,6 +268,9 @@ 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(', ')));
@ -300,7 +302,7 @@ class Attribute implements \Countable, \ArrayAccess
{
return $this->schema->used_in_object_classes
->keys()
->intersect($this->oc)
->intersect($this->schema->heirachy($this->oc))
->count() === 0;
}
@ -327,10 +329,9 @@ class Attribute implements \Countable, \ArrayAccess
* @param bool $old Use old value
* @param bool $new Enable adding 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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
if ($this->is_internal)
// @note Internal attributes cannot be edited
@ -351,7 +352,6 @@ class Attribute implements \Countable, \ArrayAccess
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('template',$template)
->with('updated',$updated);
}

View File

@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary;
use App\Classes\Template;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@ -15,7 +15,7 @@ final class JpegPhoto extends Binary
{
use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)

View File

@ -4,7 +4,6 @@ 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;
@ -18,15 +17,6 @@ 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-----",
@ -39,23 +29,19 @@ 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(int $key=0): Carbon
public function expires($key=0): Carbon
{
return Carbon::createFromTimestampUTC($this->cert_info('validTo_time_t',$key));
}
public function subject(int $key=0): string
public function subject($key=0): string
{
$subject = collect($this->cert_info('subject',$key))->reverse();
return $subject->map(fn($item,$key)=>sprintf("%s=%s",$key,$item))->join(',');
}
public function subject_key_identifier(int $key=0): string
{
return $this->cert_info('extensions.subjectKeyIdentifier',$key);
}
}

View File

@ -2,6 +2,9 @@
namespace App\Classes\LDAP\Attribute;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;

View File

@ -3,6 +3,7 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Classes\LDAP\Attribute;
@ -58,6 +59,8 @@ 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);
}
}

View File

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

View File

@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute;
use App\Classes\Template;
use App\Ldap\Entry;
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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
return view('components.attribute.krbprincipalkey')
->with('o',$this)

View File

@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Classes\Template;
use App\Ldap\Entry;
/**
* 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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
return view('components.attribute.krbticketflags')
->with('o',$this)

View File

@ -6,7 +6,7 @@ use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Classes\Template;
use App\Ldap\Entry;
/**
* 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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
return view('components.attribute.objectclass')
->with('o',$this)

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Classes\Template;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@ -80,14 +80,13 @@ final class Password extends Attribute
return ($helpers=static::helpers())->has($id) ? new ($helpers->get($id)) : NULL;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): 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());
}

View File

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

View File

@ -2,10 +2,12 @@
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
@ -51,4 +53,11 @@ 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,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.internal')
->with('o',$this);
}
}

View File

@ -5,14 +5,14 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Classes\Template;
use App\Ldap\Entry;
/**
* Represents a Generic Schema Attribute
*/
class Generic extends Schema
{
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.generic')

View File

@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Classes\Template;
use App\Ldap\Entry;
/**
* 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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.mechanisms')

View File

@ -5,7 +5,7 @@ namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
use App\Classes\Template;
use App\Ldap\Entry;
/**
* 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,bool $updated=FALSE,?Template $template=NULL): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.oid')

View File

@ -29,7 +29,7 @@ abstract class Export
abstract public function __toString(): string;
protected function header(): string
protected function header()
{
$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'),request()->root(),date('F j, Y g:i a')).$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',__('Exported by'),Auth::user() ?: 'Anonymous').$this->br;
$output .= sprintf('# %s: %s',__('Version'),config('app.version')).$this->br;

View File

@ -7,74 +7,304 @@ 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
{
private const LOGKEY = 'SAT';
final class AttributeType extends Base {
// The attribute from which this attribute inherits (if any)
private ?string $sup_attribute = NULL;
// An array of AttributeTypes which inherit from this one
private(set) Collection $children;
// Array of AttributeTypes which inherit from this one
private Collection $children;
// The equality rule used
private(set) ?string $equality = NULL;
// This attribute has been forced a MAY attribute by the configuration.
private(set) bool $forced_as_may = FALSE;
// boolean: is collective?
private(set) bool $is_collective = FALSE;
// Is this a must attribute
private(set) bool $is_must = FALSE;
// boolean: can use modify?
private(set) bool $is_no_user_modification = FALSE;
// boolean: is single valued only?
private(set) bool $is_single_value = FALSE;
// The max number of characters this attribute can be
private(set) ?int $max_length = NULL;
// An array of names (including aliases) that this attribute is known by
private(set) Collection $names;
private ?string $equality = NULL;
// The ordering of the attributeType
private(set) ?string $ordering = NULL;
// A list of object class names that require this attribute type.
private(set) Collection $required_by_object_classes;
// Which objectclass is defining this attribute for an Entry
public ?string $source = NULL;
private ?string $ordering = NULL;
// Supports substring matching?
private(set) ?string $sub_str_rule = NULL;
// The attribute from which this attribute inherits (if any)
private(set) ?string $sup_attribute = NULL;
private ?string $sub_str_rule = NULL;
// The full syntax string, ie 1.2.3.4{16}
private(set) ?string $syntax = NULL;
private(set) ?string $syntax_oid = NULL;
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(set) ?string $usage = NULL;
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(set) Collection $used_in_object_classes;
private Collection $used_in_object_classes;
// A list of object class names that require this attribute type.
private Collection $required_by_object_classes;
// This attribute has been forced a MAY attribute by the configuration.
private 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));
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
// Init
$this->children = collect();
$this->aliases = collect();
$this->used_in_object_classes = collect();
$this->required_by_object_classes = collect();
for ($i=0; $i < count($strings); $i++) {
switch ($strings[$i]) {
case '(':
case ')':
break;
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];
} while (! preg_match("/\'$/s",$strings[$i]));
// This attribute has no aliases
//$this->aliases = collect();
} else {
$i++;
do {
// In case we came here becaues of a ('
if (preg_match('/^\(/',$strings[$i]))
$strings[$i] = preg_replace('/^\(/','',$strings[$i]);
else
$i++;
$this->name .= ($this->name ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
// Add alias names for this attribute
while ($strings[++$i] != ')') {
$alias = $strings[$i];
$alias = preg_replace("/^\'(.*)\'$/",'$1',$alias);
$this->addAlias($alias);
}
}
$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;
}
public function __get(string $key): mixed
{
return match ($key) {
'names_lc' => $this->names->map('strtolower'),
default => parent::__get($key)
};
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);
}
/**
@ -85,8 +315,7 @@ final class AttributeType extends Base
*/
public function addChild(string $child): void
{
$this->children
->push($child);
$this->children->push($child);
}
/**
@ -117,10 +346,144 @@ final class AttributeType extends Base
private function factory(): Attribute
{
return Attribute\Factory::create(
dn:'',
attribute:$this->name,
values:[]);
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;
}
/**
@ -129,162 +492,58 @@ final class AttributeType extends Base
* @param Collection $ocs
* @return Collection
*/
private function heirachy(Collection $ocs): Collection
public function heirachy(Collection $ocs): Collection
{
$result = collect();
foreach ($ocs as $oc) {
$item = config('server')
->schema('objectclasses',$oc);
$schema = config('server')
->schema('objectclasses',$oc)
->getParents(TRUE)
->pluck('name');
$result = $result
->merge($item
->getParents(TRUE)
->pluck('oid'))
->push($item->oid);
$result = $result->merge($schema)->push($oc);
}
return $result;
}
/**
* 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 )
* @return bool
* @deprecated use $this->forced_as_may
*/
protected function parse(string $line): void
public function isForceMay(): bool
{
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);
return $this->forced_as_may;
}
protected function parse_chunk(array $strings,int &$i): void
/**
* Removes an attribute name from this attribute's alias array.
*
* @param string $alias The name of the attribute to remove.
*/
public function removeAlias(string $alias): void
{
switch ($strings[$i]) {
case 'NAME':
$name = '';
if (($x=$this->aliases->search($alias)) !== FALSE)
$this->aliases->forget($x);
}
// @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];
/**
* 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
{
$this->aliases = $aliases;
}
} 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);
}
/**
* This function will mark this attribute as a forced MAY attribute
*/
public function setForceMay() {
$this->forced_as_may = TRUE;
}
/**
@ -298,28 +557,13 @@ final class AttributeType extends Base
}
/**
* If this is a MUST attribute to the objectclass that defines it
* Sets this attribute's SUP attribute (ie, the attribute from which this attribute inherits).
*
* @return void
* @param string $attr The name of the new parent (SUP) attribute
*/
public function setMust(): void
public function setSupAttribute(string $attr): void
{
$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;
$this->sup_attribute = trim($attr);
}
/**

View File

@ -2,8 +2,6 @@
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Facades\Log;
use App\Exceptions\InvalidUsage;
/**
@ -12,38 +10,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
{
private const LOGKEY = 'Sb-';
abstract class Base {
protected const DEBUG_VERBOSE = FALSE;
// Record the LDAP String
private(set) string $line;
private string $line;
// The schema item's name.
protected(set) string $name = '';
protected string $name = '';
// The OID of this schema item.
protected(set) string $oid = '';
protected string $oid = '';
# The description of this schema item.
protected(set) string $description = '';
protected string $description = '';
// Boolean value indicating whether this objectClass is obsolete
private(set) bool $is_obsolete = FALSE;
private 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);
@ -56,95 +54,69 @@ 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;
}
protected function parse(string $line): void
public function setDescription(string $desc): void
{
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
for ($i=0; $i < count($strings); $i++) {
$this->parse_chunk($strings,$i);
}
$this->description = $desc;
}
protected function parse_chunk(array $strings,int &$i): void
/**
* Sets this attribute's name.
*
* @param string $name The new name to give this attribute.
*/
public function setName($name): void
{
switch ($strings[$i]) {
case '(':
case ')':
break;
$this->name = $name;
}
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]));
}
public function setOID(string $oid): void
{
$this->oid = $oid;
}
}

View File

@ -6,49 +6,74 @@ use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP Syntax
*
* @package phpLDAPadmin
* @subpackage Schema
*/
final class LDAPSyntax extends Base
{
private const LOGKEY = 'SLS';
final class LDAPSyntax extends Base {
// Is human readable?
private(set) ?bool $is_not_human_readable = NULL;
private ?bool $is_not_human_readable = NULL;
// Binary transfer required?
private(set) ?bool $binary_transfer_required = NULL;
private ?bool $binary_transfer_required = NULL;
/**
* Creates a new Syntax object from a raw LDAP syntax string.
*/
protected function parse(string $line): void
{
if (static::DEBUG_VERBOSE)
Log::debug(sprintf('%s:Parsing LDAPSyntax [%s]',self::LOGKEY,$line));
public function __construct(string $line) {
Log::debug(sprintf('Parsing LDAPSyntax [%s]',$line));
parent::parse($line);
}
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
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');
if (static::DEBUG_VERBOSE)
Log::debug(sprintf('%s:- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',self::LOGKEY,$this->binary_transfer_required));
Log::debug(sprintf('- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',$this->binary_transfer_required));
break;
case 'X-NOT-HUMAN-READABLE':
$this->is_not_human_readable = (str_replace("'",'',$strings[++$i]) === 'TRUE');
if (static::DEBUG_VERBOSE)
Log::debug(sprintf('%s:- Case X-NOT-HUMAN-READABLE returned (%s)',self::LOGKEY,$this->is_not_human_readable));
Log::debug(sprintf('- Case X-NOT-HUMAN-READABLE returned (%s)',$this->is_not_human_readable));
break;
default:
parent::parse_chunk($strings,$i);
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 'binary_transfer_required': return $this->binary_transfer_required;
case 'is_not_human_readable': return $this->is_not_human_readable;
default: return parent::__get($key);
}
}
}

View File

@ -7,16 +7,106 @@ use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP MatchingRule
*
* @package phpLDAPadmin
* @subpackage Schema
*/
final class MatchingRule extends Base
{
private const LOGKEY = 'SMR';
final class MatchingRule extends Base {
// This rule's syntax OID
private(set) ?string $syntax = NULL;
private ?string $syntax = NULL;
// An array of attribute names who use this MatchingRule
private(set) Collection $used_by_attrs;
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);
}
}
/**
* Adds an attribute name to the list of attributes who use this MatchingRule
@ -30,33 +120,23 @@ final class MatchingRule extends Base
}
/**
* Creates a new MatchingRule object from a raw LDAP MatchingRule string.
* Gets an array of attribute names (strings) which use this MatchingRule
*
* @param string $line
* @return void
* @return array The array of attribute names (strings).
* @deprecated use $this->used_by_attrs
*/
protected function parse(string $line): void
public function getUsedByAttrs()
{
if (static::DEBUG_VERBOSE)
Log::debug(sprintf('%s:Parsing MatchingRule [%s]',self::LOGKEY,$line));
// Init
$this->used_by_attrs = collect();
parent::parse($line);
return $this->used_by_attrs;
}
protected function parse_chunk(array $strings,int &$i): void
/**
* 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
{
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);
}
$this->used_by_attrs = $attrs;
}
}

View File

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

View File

@ -10,28 +10,206 @@ 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(set) Collection $sup_classes;
private Collection $sup_classes;
// One of STRUCTURAL, ABSTRACT, or AUXILIARY
private int $type;
// Attributes that this objectclass defines
private(set) Collection $attributes;
// 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]);
}
}
}
public function __get(string $key): mixed
{
return match ($key) {
'all_attributes' => $this->getMustAttrs(TRUE)
->merge($this->getMayAttrs(TRUE)),
'attributes' => $this->getAllAttrs(TRUE),
'sup' => $this->sup_classes,
'type_name' => match ($this->type) {
Server::OC_STRUCTURAL => 'Structural',
Server::OC_ABSTRACT => 'Abstract',
@ -42,6 +220,23 @@ 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.
@ -50,8 +245,57 @@ final class ObjectClass extends Base
*/
public function addChildObjectClass(string $name): void
{
if (! $this->child_classes->contains($name))
$this->child_classes->push($name);
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;
}
/**
@ -69,26 +313,42 @@ final class ObjectClass extends Base
*/
public function getMayAttrs(bool $parents=FALSE): Collection
{
$attrs = $this->attributes
->filter(fn($item)=>! $item->is_must)
->transform(function($item) {
$item->source = $this->name;
return $item;
});
// 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));
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;
}));
$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; });
// Return a sorted list
return $attrs
->unique(fn($item)=>$item->name)
->sortBy(fn($item)=>$item->name);
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');
}
/**
@ -105,26 +365,41 @@ final class ObjectClass extends Base
*/
public function getMustAttrs(bool $parents=FALSE): Collection
{
$attrs = $this->attributes
->filter(fn($item)=>$item->is_must)
->transform(function($item) {
$item->source = $this->name;
return $item;
});
// 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); });
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;
}));
$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; });
// Return a sorted list
return $attrs
->unique(fn($item)=>$item->name)
->sortBy(fn($item)=>$item->name);
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');
}
/**
@ -151,6 +426,27 @@ 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
*
@ -161,107 +457,37 @@ final class ObjectClass extends Base
return $this->type === Server::OC_AUXILIARY;
}
public function isStructural(): bool
/**
* Determine if an array is listed in the may_force attrs
*/
public function isForceMay(string $attr): bool
{
return $this->type === Server::OC_STRUCTURAL;
return $this->may_force->ppluck('name')->contains($attr);
}
/**
* Creates a new ObjectClass object given a raw LDAP objectClass string.
* Return if this objectClass is related to $oclass
*
* eg: ( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )
*
* @param string $line Schema Line
* @param array $oclass ObjectClasses that this attribute may be related to
* @return bool
* @throws InvalidUsage
*/
protected function parse(string $line): void
public function isRelated(array $oclass): bool
{
Log::debug(sprintf('%s:Parsing ObjectClass [%s]',self::LOGKEY,$line));
// If I am in the array, we'll just return false
if (in_array_ignore_case($this->name,$oclass))
return FALSE;
// Init
$this->attributes = collect();
$this->sup_classes = collect();
$this->child_classes = collect();
foreach ($oclass as $object_class)
if ($object_class->isStructural() && in_array_ignore_case($this->name,$object_class->getParents()->pluck('name')))
return TRUE;
parent::parse($line);
return FALSE;
}
protected function parse_chunk(array $strings,int &$i): void
public function isStructural(): bool
{
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);
}
return $this->type === Server::OC_STRUCTURAL;
}
/**

View File

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

View File

@ -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,ObjectClass};
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass};
use App\Exceptions\InvalidUsage;
use App\Ldap\Entry;
@ -28,10 +28,9 @@ 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;
@ -39,8 +38,6 @@ final class Server
public function __construct()
{
$this->rootDSE = self::rootDSE();
$this->attributetypes = collect();
$this->ldapsyntaxes = collect();
$this->matchingrules = collect();
@ -50,6 +47,10 @@ 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),
@ -66,10 +67,10 @@ final class Server
* @return Collection
* @testedin GetBaseDNTest::testBaseDNExists();
*/
public static function baseDNs(bool $objects=TRUE): Collection
public static function baseDNs(bool $objects=FALSE): Collection
{
try {
$namingcontexts = collect(config('pla.base_dns') ?: self::rootDSE()?->namingcontexts);
$rootdse = self::rootDSE();
/**
* LDAP Error Codes:
@ -175,16 +176,16 @@ final class Server
}
if (! $objects)
return $namingcontexts;
return collect($rootdse->namingcontexts);
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($namingcontexts) {
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($rootdse) {
$result = collect();
// @note: Incase our rootDSE didnt return a namingcontext, we'll have no base DNs
foreach ($namingcontexts as $dn)
foreach (($rootdse->namingcontexts ?: []) as $dn)
$result->push(self::get($dn)->read()->find($dn));
return $result->filter()->sort(fn($item)=>$item->sort_key);
return $result->filter();
});
}
@ -256,6 +257,18 @@ 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 */
/**
@ -275,6 +288,7 @@ final class Server
'c' // Needed for the tree to show icons for countries
]))
->list()
->orderBy('dn')
->get() ?: NULL;
}
@ -293,37 +307,15 @@ final class Server
}
/**
* Get an attribute key for an attributetype name
* This function determines if the specified attribute is contained in the force_may list
* as configured in config.php.
*
* @param string $key
* @return int|bool
* @throws InvalidUsage
* @return boolean True if the specified attribute is configured to be force as a may attribute
* @todo There are 3 isForceMay() functions - we only need one
*/
public function get_attr_id(string $key): int|bool
public function isForceMay($attr_name): bool
{
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;
return in_array($attr_name,config('pla.force_may',[]));
}
/**
@ -344,167 +336,212 @@ 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);
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']);
$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 our schema's null, we didnt find it.
if (! $schema)
throw new Exception('Couldnt find schema at:'.$schema_dn);
break;
switch ($item) {
case 'attributetypes':
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
// This error message is not localized as only developers should ever see it
default:
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
}
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
// 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']);
$o = new AttributeType($line);
$this->attributetypes->push($o);
}
// If our schema's null, we didnt find it.
if (! $schema)
throw new Exception('Couldnt find schema at:'.$schema_dn);
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);
switch ($item) {
case 'attributetypes':
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
if (! $this->attributetypes[$attrid]->children->contains($o->oid))
$this->attributetypes[$attrid]->addChild($o->oid);
}
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
// 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);
$o = new AttributeType($line);
$this->attributetypes->put($o->name_lc,$o);
}
/* 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);
// 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);
}
}
}
return $this->attributetypes;
// Now go through and reference the parent/child relationships
foreach ($this->attributetypes as $o)
if ($o->sup_attribute) {
$parent = strtolower($o->sup_attribute);
case 'ldapsyntaxes':
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
if ($this->attributetypes->has($parent) !== FALSE)
$this->attributetypes[$parent]->addChild($o->name);
}
foreach ($schema->{$item} as $line) {
// 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) {
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);
$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());
}
} 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_id = $this->matchingrules->search(fn($item)=>$item->oid === $attr->equality);
$rule_key = strtolower($attr->getEquality());
if ($rule_id !== FALSE)
$this->matchingrules[$rule_id]->addUsedByAttr($attr->name);
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);
}
return $this->matchingrules;
return $this->objectclasses;
case 'objectclasses':
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
// Shouldnt get here
default:
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
}
});
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));
}
return is_null($key) ? $result : $result->get($key);
}
/**
* Get the Schema DN
* Given an OID, return the ldapsyntax for the OID
*
* @return string
* @throws ObjectNotFoundException
* @param string $oid
* @return LDAPSyntax|null
*/
public function schemaDN(): string
public function schemaSyntaxName(string $oid): ?LDAPSyntax
{
return Arr::get($this->rootDSE->subschemasubentry,0);
return $this->schema('ldapsyntaxes',$oid);
}
}

View File

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

View File

@ -12,24 +12,26 @@ 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
{
return Server::baseDNs()
->map(fn($item)=> [
'title'=>$item->getRdn(),
'item'=>$item->getDNSecure(),
'lazy'=>TRUE,
'icon'=>'fa-fw fas fa-sitemap',
'tooltip'=>$item->getDn(),
])->values();
$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(),
]);
}
/**
@ -38,13 +40,13 @@ class AjaxController extends Controller
*/
public function children(Request $request): Collection
{
$dn = Crypt::decryptString($request->query('_key'));
$dn = Crypt::decryptString($request->query('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]',self::LOGKEY,$dn));
Log::debug(sprintf('%s: Query [%s]',__METHOD__,$dn));
return (config('server'))
->children($dn)

View File

@ -7,11 +7,8 @@ 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
{
@ -54,45 +51,6 @@ 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
*

View File

@ -6,18 +6,17 @@ 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};
@ -27,9 +26,20 @@ use App\Ldap\Entry;
class HomeController extends Controller
{
private const LOGKEY = 'CHc';
private function bases(): Collection
{
$base = Server::baseDNs(TRUE) ?: collect();
private const INTERNAL_POST = ['_auto_value','_key','_rdn','_rdn_value','_step','_template','_token','_userpassword_hash'];
return $base->transform(function($item) {
return [
'title'=>$item->getRdn(),
'item'=>$item->getDNSecure(),
'lazy'=>TRUE,
'icon'=>'fa-fw fas fa-sitemap',
'tooltip'=>$item->getDn(),
];
});
}
/**
* Create a new object in the LDAP server
@ -40,45 +50,30 @@ 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']);
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);
if (count($x=array_filter(old('objectclass',$request->objectclass)))) {
$o->objectclass = $x;
// Also add in our required attributes
foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->is_must) as $ao)
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$o->setRDNBase($key['dn']);
}
$step = $request->get('_step') ? $request->get('_step')+1 : old('_step');
$step = $request->step ? $request->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']));
}
@ -108,7 +103,6 @@ class HomeController extends Controller
return $view
->with('o',$o)
->with('langtag',Entry::TAG_NOTAG)
->with('template',NULL)
->with('updated',FALSE);
}
@ -116,21 +110,14 @@ class HomeController extends Controller
{
$key = $this->request_key($request,collect(old()));
$dn = sprintf('%s=%s,%s',$request->get('_rdn'),$request->get('_rdn_value'),$key['dn']);
$dn = sprintf('%s=%s,%s',$request->rdn,$request->rdn_value,$key['dn']);
$o = new Entry;
$o->setDn($dn);
foreach ($request->except(self::INTERNAL_POST) as $key => $value)
foreach ($request->except(['_token','key','step','rdn','rdn_value','userpassword_hash']) 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();
@ -159,12 +146,6 @@ 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());
}
@ -230,7 +211,7 @@ class HomeController extends Controller
*/
public function entry_objectclass_add(Request $request): Collection
{
$dn = $request->get('_key') ? Crypt::decryptString($request->dn) : '';
$dn = $request->key ? Crypt::decryptString($request->dn) : '';
$oc = Factory::create($dn,'objectclass',$request->oc);
$ocs = $oc
@ -285,17 +266,31 @@ 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)
$o->userpassword = $this->password(
$o->getObject('userpassword'),
$request->userpassword,
$request->get('_userpassword_hash'));
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 (! $o->getDirty())
return back()
@ -303,6 +298,7 @@ class HomeController extends Controller
->with('note',__('No attributes changed'));
return view('update')
->with('bases',$this->bases())
->with('dn',$dn)
->with('o',$o);
}
@ -372,7 +368,6 @@ 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
{
@ -382,28 +377,22 @@ class HomeController extends Controller
$key = $this->request_key($request,$old);
$view = $old
$view = ($old
? view('frame')->with('subframe',$key['cmd'])
: view('frames.'.$key['cmd']);
: view('frames.'.$key['cmd']))
->with('bases',$this->bases());
// If we are rendering a DN, rebuild our object
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']
if ($key['dn']) {
$o = config('server')->fetch($key['dn']);
foreach (collect(old())->except(array_merge(self::INTERNAL_POST,['dn'])) as $attr => $value)
foreach (collect(old())->except(['key','dn','step','_token','userpassword_hash','rdn','rdn_value']) 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
@ -431,7 +420,8 @@ class HomeController extends Controller
// Did we come here as a result of a redirect
return count(old())
? $this->frame($request,collect(old()))
: view('home');
: view('home')
->with('bases',$this->bases());
}
/**
@ -467,32 +457,11 @@ 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
*
@ -505,8 +474,8 @@ class HomeController extends Controller
// Setup
$cmd = NULL;
$dn = NULL;
$key = $request->get('_key',old('_key'))
? Crypt::decryptString($request->get('_key',old('_key')))
$key = $request->get('key',old('key'))
? Crypt::decryptString($request->get('key',old('key')))
: NULL;
// Determine if our key has a command
@ -518,9 +487,9 @@ class HomeController extends Controller
$dn = ($m[2] !== '_NOP') ? $m[2] : NULL;
}
} elseif (old('dn',$request->get('_key'))) {
} elseif (old('dn',$request->get('key'))) {
$cmd = 'dn';
$dn = Crypt::decryptString(old('dn',$request->get('_key')));
$dn = Crypt::decryptString(old('dn',$request->get('key')));
}
return ['cmd'=>$cmd,'dn'=>$dn];
@ -537,12 +506,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->get('_key') && (! config('server')->schema($request->type)->has($request->get('_key'))))
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
abort(404);
return view('frames.schema')
->with('type',$request->type)
->with('key',$request->get('_key'));
->with('key',$request->key);
}
/**

View File

@ -22,7 +22,7 @@ class SearchController extends Controller
$result = collect();
foreach ($so->baseDNs(FALSE) as $base) {
foreach ($so->baseDNs() as $base) {
$search = (new Entry)
->in($base);

View File

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

View File

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

View File

@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Classes\LDAP\Server;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
@ -30,7 +29,7 @@ class SwapinAuthUser
if (! array_key_exists($key,config('ldap.connections')))
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
if (($request->path() !== 'logout') && Session::has('username_encrypt') && Session::has('password_encrypt')) {
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Crypt::decryptString(Session::get('username_encrypt')));
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
@ -44,8 +43,6 @@ 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);
}
}

View File

@ -3,17 +3,12 @@
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.
*
@ -22,8 +17,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.'),
];
}
@ -40,23 +35,14 @@ class EntryAddRequest extends FormRequest
return [];
$r = request() ?: collect();
$rk = array_keys($r->all());
return config('server')
->schema('attributetypes')
->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;
})
->intersectByKeys($r->all())
->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) {
@ -71,68 +57,20 @@ 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_' => [
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'));
},
'objectclass._null_'=>[
'required',
'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();

View File

@ -10,24 +10,14 @@ 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')
->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;
})
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->filter()
->flatMap(fn($item)=>$item)

View File

@ -3,16 +3,10 @@
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;
@ -44,31 +38,10 @@ class Entry extends Model
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->objects = collect();
$this->templates = collect(['default'=>__('LDAP Entry')]);
// 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;
});
parent::__construct($attributes);
}
public function discardChanges(): static
@ -158,17 +131,6 @@ 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;
}
@ -208,13 +170,13 @@ class Entry extends Model
// If the attribute name has tags
list($attribute,$tag) = $this->keytag($key);
if (config('server')->get_attr_id($attribute) === FALSE)
if (! config('server')->schema('attributetypes')->has($attribute))
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($key,$o);
$this->objects->put($attribute,$o);
}
/**
@ -318,7 +280,7 @@ class Entry extends Model
$result = collect();
foreach (($this->getObject('objectclass')?->values ?: []) as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->all_attributes);
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result;
}
@ -421,6 +383,9 @@ 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
{
@ -433,7 +398,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->is_must));
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
return $o;
}
@ -465,21 +430,6 @@ 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
*
@ -580,13 +530,5 @@ 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);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Laravel\LdapRecord;
@ -28,5 +29,11 @@ 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());
}
}

View File

@ -24,7 +24,6 @@ class HasStructuralObjectClass implements ValidationRule
if ($item && config('server')->schema('objectclasses',$item)->isStructural())
return;
if (collect($value)->dot()->filter()->count())
$fail(__('There isnt a Structural Objectclass.'));
$fail('There isnt a Structural Objectclass.');
}
}

View File

@ -6,7 +6,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Classes\Template;
class Attribute extends Component
{
@ -15,19 +14,17 @@ class Attribute extends Component
public bool $new;
public bool $old;
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,bool $updated=FALSE,?Template $template=NULL)
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE)
{
$this->o = $o;
$this->edit = $edit;
$this->old = $old;
$this->new = $new;
$this->updated = $updated;
$this->template = $template;
}
/**
@ -39,12 +36,7 @@ class Attribute extends Component
{
return $this->o
? $this->o
->render(
edit: $this->edit,
old: $this->old,
new: $this->new,
updated: $this->updated,
template: $this->template)
->render(edit: $this->edit,old: $this->old,new: $this->new,updated: $this->updated)
: __('Unknown');
}
}

View File

@ -4,7 +4,7 @@ use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\{AcceptLanguage,AllowAnonymous,CheckUpdate,SwapinAuthUser,ViewVariables};
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,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: [
AcceptLanguage::class,
ApplicationSession::class,
AllowAnonymous::class,
SwapinAuthUser::class,
ViewVariables::class,

View File

@ -15,7 +15,6 @@
"laravel/ui": "^4.5"
},
"require-dev": {
"amirami/localizator": "^0.14@dev",
"barryvdh/laravel-debugbar": "^3.6",
"fakerphp/faker": "^1.23",
"mockery/mockery": "^1.6",

251
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "baf49824fa0f2a2a61b7915204b18d2c",
"content-hash": "fe524438312a0b5d565ad83c191eac6a",
"packages": [
{
"name": "brick/math",
"version": "0.13.1",
"version": "0.12.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
"shasum": ""
},
"require": {
@ -56,7 +56,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.13.1"
"source": "https://github.com/brick/math/tree/0.12.3"
},
"funding": [
{
@ -64,7 +64,7 @@
"type": "github"
}
],
"time": "2025-03-29T13:50:30+00:00"
"time": "2025-02-28T13:11:00+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
@ -288,16 +288,16 @@
},
{
"name": "directorytree/ldaprecord-laravel",
"version": "v3.4.2",
"version": "v3.4.1",
"source": {
"type": "git",
"url": "https://github.com/DirectoryTree/LdapRecord-Laravel.git",
"reference": "28c5a7aa42aa3fa631f9c0f0c8236fd19bc7b00c"
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/28c5a7aa42aa3fa631f9c0f0c8236fd19bc7b00c",
"reference": "28c5a7aa42aa3fa631f9c0f0c8236fd19bc7b00c",
"url": "https://api.github.com/repos/DirectoryTree/LdapRecord-Laravel/zipball/15f56e01319852d41023633d3688ac4aa139aa6e",
"reference": "15f56e01319852d41023633d3688ac4aa139aa6e",
"shasum": ""
},
"require": {
@ -343,7 +343,7 @@
],
"support": {
"issues": "https://github.com/DirectoryTree/LdapRecord-Laravel/issues",
"source": "https://github.com/DirectoryTree/LdapRecord-Laravel/tree/v3.4.2"
"source": "https://github.com/DirectoryTree/LdapRecord-Laravel/tree/v3.4.1"
},
"funding": [
{
@ -351,7 +351,7 @@
"type": "github"
}
],
"time": "2025-06-13T15:46:25+00:00"
"time": "2025-03-21T19:16:44+00:00"
},
{
"name": "doctrine/inflector",
@ -1199,20 +1199,20 @@
},
{
"name": "laravel/framework",
"version": "v12.19.3",
"version": "v12.16.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262"
"reference": "293bb1c70224faebfd3d4328e201c37115da055f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
"url": "https://api.github.com/repos/laravel/framework/zipball/293bb1c70224faebfd3d4328e201c37115da055f",
"reference": "293bb1c70224faebfd3d4328e201c37115da055f",
"shasum": ""
},
"require": {
"brick/math": "^0.11|^0.12|^0.13",
"brick/math": "^0.11|^0.12",
"composer-runtime-api": "^2.2",
"doctrine/inflector": "^2.0.5",
"dragonmantank/cron-expression": "^3.4",
@ -1410,7 +1410,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-06-18T12:56:23+00:00"
"time": "2025-05-27T15:49:44+00:00"
},
{
"name": "laravel/prompts",
@ -2315,16 +2315,16 @@
},
{
"name": "nesbot/carbon",
"version": "3.10.0",
"version": "3.9.1",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9"
"reference": "ced71f79398ece168e24f7f7710462f462310d4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
"reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d",
"reference": "ced71f79398ece168e24f7f7710462f462310d4d",
"shasum": ""
},
"require": {
@ -2332,9 +2332,9 @@
"ext-json": "*",
"php": "^8.1",
"psr/clock": "^1.0",
"symfony/clock": "^6.3.12 || ^7.0",
"symfony/clock": "^6.3 || ^7.0",
"symfony/polyfill-mbstring": "^1.0",
"symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0"
"symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0"
},
"provide": {
"psr/clock-implementation": "1.0"
@ -2342,13 +2342,14 @@
"require-dev": {
"doctrine/dbal": "^3.6.3 || ^4.0",
"doctrine/orm": "^2.15.2 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.75.0",
"friendsofphp/php-cs-fixer": "^3.57.2",
"kylekatarnls/multi-tester": "^2.5.3",
"ondrejmirtes/better-reflection": "^6.25.0.4",
"phpmd/phpmd": "^2.15.0",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.17",
"phpunit/phpunit": "^10.5.46",
"squizlabs/php_codesniffer": "^3.13.0"
"phpstan/extension-installer": "^1.3.1",
"phpstan/phpstan": "^1.11.2",
"phpunit/phpunit": "^10.5.20",
"squizlabs/php_codesniffer": "^3.9.0"
},
"bin": [
"bin/carbon"
@ -2416,7 +2417,7 @@
"type": "tidelift"
}
],
"time": "2025-06-12T10:24:28+00:00"
"time": "2025-05-01T19:51:51+00:00"
},
{
"name": "nette/schema",
@ -2482,16 +2483,16 @@
},
{
"name": "nette/utils",
"version": "v4.0.7",
"version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2"
"reference": "ce708655043c7050eb050df361c5e313cf708309"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
"reference": "ce708655043c7050eb050df361c5e313cf708309",
"shasum": ""
},
"require": {
@ -2562,9 +2563,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.7"
"source": "https://github.com/nette/utils/tree/v4.0.6"
},
"time": "2025-06-03T04:55:08+00:00"
"time": "2025-03-30T21:06:30+00:00"
},
{
"name": "nunomaduro/termwind",
@ -3262,16 +3263,16 @@
},
{
"name": "ramsey/uuid",
"version": "4.8.1",
"version": "4.8.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28"
"reference": "6700833915c00f890615fbcb653faed513836836"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/6700833915c00f890615fbcb653faed513836836",
"reference": "6700833915c00f890615fbcb653faed513836836",
"shasum": ""
},
"require": {
@ -3335,9 +3336,9 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.8.1"
"source": "https://github.com/ramsey/uuid/tree/4.8.0"
},
"time": "2025-06-01T06:28:46+00:00"
"time": "2025-06-01T02:32:15+00:00"
},
{
"name": "symfony/clock",
@ -5847,84 +5848,6 @@
}
],
"packages-dev": [
{
"name": "amirami/localizator",
"version": "v0.14.0-alpha",
"source": {
"type": "git",
"url": "https://github.com/amiranagram/localizator.git",
"reference": "30aae5e51d3aaa95105e3834d481b8c37efaab40"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amiranagram/localizator/zipball/30aae5e51d3aaa95105e3834d481b8c37efaab40",
"reference": "30aae5e51d3aaa95105e3834d481b8c37efaab40",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/config": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0",
"php": "^7.2.5|^8.0",
"symfony/finder": "^5.1|^6.0|^7.0"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench": "^6.6|^7.0|^8.0|^9.0|^10.0",
"phpunit/phpunit": "^9.5|^10.0|^11.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Amirami\\Localizator\\ServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Amirami\\Localizator\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Amir Rami",
"email": "me@amirrami.com",
"homepage": "https://amirrami.com",
"role": "Developer"
}
],
"description": "Localizator is a small tool for Laravel that gives you the ability to extract untranslated strings from project files. It works using the artisan command line and the provided localize command.",
"homepage": "https://github.com/amiranagram/localizator",
"support": {
"issues": "https://github.com/amiranagram/localizator/issues",
"source": "https://github.com/amiranagram/localizator/tree/v0.14.0-alpha"
},
"funding": [
{
"url": "https://m.do.co/c/f38828dd20f8",
"type": "custom"
},
{
"url": "https://www.buymeacoffee.com/amirami/",
"type": "custom"
},
{
"url": "https://github.com/amiranagram",
"type": "github"
}
],
"time": "2025-03-06T12:49:02+00:00"
},
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.15.4",
@ -6075,16 +5998,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.3",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "59a123a3d459c5a23055802237cb317f609867e5"
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5",
"reference": "59a123a3d459c5a23055802237cb317f609867e5",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"shasum": ""
},
"require": {
@ -6134,7 +6057,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.3"
"source": "https://github.com/filp/whoops/tree/2.18.0"
},
"funding": [
{
@ -6142,7 +6065,7 @@
"type": "github"
}
],
"time": "2025-06-16T00:02:10+00:00"
"time": "2025-03-15T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -6398,23 +6321,23 @@
},
{
"name": "nunomaduro/collision",
"version": "v8.8.1",
"version": "v8.8.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5"
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
"reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8",
"shasum": ""
},
"require": {
"filp/whoops": "^2.18.1",
"nunomaduro/termwind": "^2.3.1",
"filp/whoops": "^2.18.0",
"nunomaduro/termwind": "^2.3.0",
"php": "^8.2.0",
"symfony/console": "^7.3.0"
"symfony/console": "^7.2.5"
},
"conflict": {
"laravel/framework": "<11.44.2 || >=13.0.0",
@ -6422,15 +6345,15 @@
},
"require-dev": {
"brianium/paratest": "^7.8.3",
"larastan/larastan": "^3.4.2",
"laravel/framework": "^11.44.2 || ^12.18",
"laravel/pint": "^1.22.1",
"laravel/sail": "^1.43.1",
"laravel/sanctum": "^4.1.1",
"larastan/larastan": "^3.2",
"laravel/framework": "^11.44.2 || ^12.6",
"laravel/pint": "^1.21.2",
"laravel/sail": "^1.41.0",
"laravel/sanctum": "^4.0.8",
"laravel/tinker": "^2.10.1",
"orchestra/testbench-core": "^9.12.0 || ^10.4",
"pestphp/pest": "^3.8.2",
"sebastian/environment": "^7.2.1 || ^8.0"
"orchestra/testbench-core": "^9.12.0 || ^10.1",
"pestphp/pest": "^3.8.0",
"sebastian/environment": "^7.2.0 || ^8.0"
},
"type": "library",
"extra": {
@ -6493,7 +6416,7 @@
"type": "patreon"
}
],
"time": "2025-06-11T01:04:21+00:00"
"time": "2025-04-03T14:33:09+00:00"
},
{
"name": "phar-io/manifest",
@ -6685,16 +6608,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "11.0.10",
"version": "11.0.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "1a800a7446add2d79cc6b3c01c45381810367d76"
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76",
"reference": "1a800a7446add2d79cc6b3c01c45381810367d76",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
"shasum": ""
},
"require": {
@ -6751,27 +6674,15 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
"type": "tidelift"
}
],
"time": "2025-06-18T08:56:18+00:00"
"time": "2025-02-25T13:26:39+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -7020,16 +6931,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.24",
"version": "11.5.21",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "6b07ab1047155cf38f82dd691787a277782271dd"
"reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6b07ab1047155cf38f82dd691787a277782271dd",
"reference": "6b07ab1047155cf38f82dd691787a277782271dd",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d565e2cdc21a7db9dc6c399c1fc2083b8010f289",
"reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289",
"shasum": ""
},
"require": {
@ -7043,7 +6954,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.2",
"phpunit/php-code-coverage": "^11.0.10",
"phpunit/php-code-coverage": "^11.0.9",
"phpunit/php-file-iterator": "^5.1.0",
"phpunit/php-invoker": "^5.0.1",
"phpunit/php-text-template": "^4.0.1",
@ -7101,7 +7012,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.24"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.21"
},
"funding": [
{
@ -7125,7 +7036,7 @@
"type": "tidelift"
}
],
"time": "2025-06-20T11:31:02+00:00"
"time": "2025-05-21T12:35:00+00:00"
},
{
"name": "sebastian/cli-parser",
@ -8550,9 +8461,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"amirami/localizator": 20
},
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {

View File

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

View File

@ -1,10 +0,0 @@
<?php
return [
'disks' => [
'templates' => [
'driver' => 'local',
'root' => base_path(env('LDAP_TEMPLATE_DIR','templates')),
],
],
];

View File

@ -35,6 +35,7 @@ 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),
@ -46,6 +47,7 @@ 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),
@ -57,6 +59,7 @@ 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),
@ -69,6 +72,7 @@ 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),

View File

@ -1,59 +0,0 @@
<?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,
];

View File

@ -43,17 +43,6 @@ 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
@ -84,20 +73,7 @@ return [
* setup.
*/
'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),
],
'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
],
];

358
package-lock.json generated
View File

@ -53,9 +53,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz",
"integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz",
"integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@ -101,12 +101,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
"integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.5",
"@babel/parser": "^7.27.3",
"@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
@ -378,22 +378,22 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz",
"integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.6"
"@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz",
"integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
@ -651,9 +651,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz",
"integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==",
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz",
"integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@ -1189,9 +1189,9 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz",
"integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.4.tgz",
"integrity": "sha512-Glp/0n8xuj+E1588otw5rjJkTXfzW7FjH3IIUrfqiZOPQCd2vbg8e+DQE8jK9g4V5/zrxFW+D9WM9gboRPELpQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@ -1509,9 +1509,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
"integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@ -1550,9 +1550,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
"integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
"integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@ -2046,9 +2046,9 @@
}
},
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"license": "MIT",
"dependencies": {
"@types/connect": "*",
@ -2114,15 +2114,15 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"license": "MIT"
},
"node_modules/@types/express": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
"integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
"version": "4.17.22",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz",
"integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==",
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
@ -2166,9 +2166,9 @@
}
},
"node_modules/@types/http-errors": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"license": "MIT"
},
"node_modules/@types/http-proxy": {
@ -2245,12 +2245,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz",
"integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==",
"version": "22.15.29",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
"undici-types": "~6.21.0"
}
},
"node_modules/@types/node-forge": {
@ -2287,9 +2287,9 @@
"license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
@ -2306,9 +2306,9 @@
}
},
"node_modules/@types/serve-static": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
"version": "1.15.7",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
@ -2557,9 +2557,9 @@
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -2800,25 +2800,10 @@
"postcss": "^8.1.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@ -3035,9 +3020,9 @@
"license": "ISC"
},
"node_modules/bootstrap": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
"funding": [
{
"type": "github",
@ -3070,9 +3055,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@ -3327,9 +3312,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001724",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz",
"integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==",
"version": "1.0.30001720",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
"integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==",
"funding": [
{
"type": "opencollective",
@ -3689,12 +3674,12 @@
}
},
"node_modules/core-js-compat": {
"version": "3.43.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz",
"integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==",
"version": "3.42.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz",
"integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.0"
"browserslist": "^4.24.4"
},
"funding": {
"type": "opencollective",
@ -4340,9 +4325,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.171",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz",
"integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==",
"version": "1.5.161",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz",
"integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==",
"license": "ISC"
},
"node_modules/elliptic": {
@ -4923,31 +4908,15 @@
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@ -5662,9 +5631,9 @@
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
@ -5761,18 +5730,6 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"license": "MIT"
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@ -5878,21 +5835,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
@ -7108,53 +7050,21 @@
}
},
"node_modules/pbkdf2": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz",
"integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"license": "MIT",
"dependencies": {
"create-hash": "~1.1.3",
"create-hmac": "^1.1.7",
"ripemd160": "=2.0.1",
"safe-buffer": "^5.2.1",
"sha.js": "^2.4.11",
"to-buffer": "^1.2.0"
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4",
"ripemd160": "^2.0.1",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/pbkdf2/node_modules/create-hash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
"integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==",
"license": "MIT",
"dependencies": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"sha.js": "^2.4.0"
}
},
"node_modules/pbkdf2/node_modules/hash-base": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
"integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.1"
}
},
"node_modules/pbkdf2/node_modules/ripemd160": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
"integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==",
"license": "MIT",
"dependencies": {
"hash-base": "^2.0.0",
"inherits": "^2.0.1"
}
},
"node_modules/pe7-icon": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pe7-icon/-/pe7-icon-1.0.4.tgz",
@ -7206,19 +7116,10 @@
"node": ">=8"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"funding": [
{
"type": "opencollective",
@ -8321,9 +8222,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.89.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz",
"integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz",
"integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@ -8703,9 +8604,9 @@
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
"integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -9094,9 +8995,9 @@
}
},
"node_modules/terser": {
"version": "5.43.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"version": "5.40.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz",
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
@ -9228,26 +9129,6 @@
"integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==",
"license": "MIT"
},
"node_modules/to-buffer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
"integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
"license": "MIT",
"dependencies": {
"isarray": "^2.0.5",
"safe-buffer": "^5.2.1",
"typed-array-buffer": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/to-buffer/node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -9294,24 +9175,10 @@
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.3",
"es-errors": "^1.3.0",
"is-typed-array": "^1.1.14"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@ -9923,9 +9790,9 @@
}
},
"node_modules/webpack/node_modules/webpack-sources": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz",
"integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
@ -9987,27 +9854,6 @@
"node": ">= 8"
}
},
"node_modules/which-typed-array": {
"version": "1.1.19",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",

View File

@ -14,20 +14,11 @@ attribute#objectclass .input-group-end:not(input.form-control) {
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 {
.input-group:first-child .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;

View File

@ -262,10 +262,4 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
/* limit selection to inside the modal */
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;
}

View File

@ -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);
// When the session expires, and we are in the tree, we need to force a reload
if (location.pathname === '/')
location.reload();
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 = { url: 'ajax/bases' };
}
// Attach the fancytree widget to an existing <div id="tree"> element
@ -96,7 +96,7 @@ $(document).ready(function() {
lazyLoad: function(event,data) {
data.result = {
url: '/ajax/children',
data: {_key: data.node.data.item,depth: 1}
data: {key: data.node.data.item,depth: 1}
};
expandChildren(data.tree.rootNode);

View File

@ -1,23 +0,0 @@
/* 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');
}

View File

@ -1,81 +0,0 @@
//
// 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;
}

View File

@ -1,45 +1,6 @@
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).
This directory contains language translation files for PLA.
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:
Language files named by 2 letter iso language name (suffixed with .json)
represent the translations for that language.
* `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.
eg: en.json

10
resources/lang/dev.json Normal file
View File

@ -0,0 +1,10 @@
{
"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>"
}

View File

@ -0,0 +1,19 @@
<?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.',
];

View File

@ -0,0 +1,19 @@
<?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' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

View File

@ -0,0 +1,22 @@
<?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.",
];

View File

@ -0,0 +1,151 @@
<?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' => [],
];

View File

@ -1,143 +0,0 @@
{
"(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"
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" translate="no">
<html translate="no">
@section('htmlheader')
@include('architect::layouts.partials.htmlheader')
@show

View File

@ -17,9 +17,4 @@
@if(file_exists('js/custom.js'))
<!-- 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

View File

@ -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"></div>
<div id="search_results" class="overflow-scroll" style="height: 300px;"></div>
</div>
<button class="btn-close"></button>
</div>
@ -164,14 +164,18 @@
</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',

View File

@ -6,22 +6,7 @@
<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
<strong class="align-middle"><abbr title="{{ $o->description }}">{{ $o->name }}</abbr></strong>
@if($o->hints->count())
<sup>
[
@ -62,14 +47,7 @@
</div>
</div>
@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
<x-attribute :o="$o" :edit="$edit" :new="$new" :updated="$updated"/>
</div>
</div>

View File

@ -5,32 +5,20 @@
@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
@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="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
@disabled($o->isDynamic())>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
</div>
@else
<input type="text" @class(['form-control','mb-1','bg-success-subtle'=>$updated]) value="{{ $value }}" disabled>
@endif
@endforeach
</span>
@endforeach
@ -45,16 +33,4 @@
@endif
</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
</x-attribute.layout>

View File

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

View File

@ -3,8 +3,8 @@
@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"/>
<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)>
<div class="invalid-feedback pb-2">
@ -20,11 +20,11 @@
@endforeach
</x-attribute.layout>
@if($edit && $o->tagValuesOld($langtag)->dot()->filter()->count())
@if($edit)
<div class="row">
<div class="offset-1 col-4">
<div class="offset-1 col-4 p-2">
<span class="p-0 m-0">
<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>
<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>
</span>
</div>
</div>

View File

@ -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','is-invalid'=>$errors->get('_rdn')]) id="rdn" name="_rdn">
<select class="form-select @error('rdn')is-invalid @enderror" 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="{{ strtolower(Arr::get($option,'id')) }}" @selected(Arr::get($option,'id') == old('_rdn',$value ?? ''))>{{ Arr::get($option,'value') }}</option>
<option value="{{ 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>

View File

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

View File

@ -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">

View File

@ -1,4 +1,4 @@
<button id="form-reset" class="btn btn-sm btn-outline-danger">@lang('Reset')</button>
<button id="form-reset" class="btn btn-outline-danger">@lang('Reset')</button>
@section('page-scripts')
<script>

View File

@ -2,28 +2,27 @@
@isset($name)
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
@endisset
<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))
<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))
<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(Arr::get($option,'id') == collect(old())->dot()->get(isset($old) ? $old.'.0' : ($id ?? $name),$value ?? ''))>{{ Arr::get($option,'value') }}</option>
<option value="{{ Arr::get($option,'id') }}" @selected(isset($name) && (Arr::get($option,'id') == old($old ?? $name,$value ?? '')))>{{ Arr::get($option,'value') }}</option>
@endforeach
@else
@foreach($options->groupBy($groupby) as $group)
<optgroup label="{{ Arr::get($group->first(),$groupby) }}">
<optgroup label="{{ $groupby == 'active' ? (Arr::get($group->first(),$groupby) ? 'Active' : 'Not Active') : Arr::get($group->first(),$groupby) }}">
@foreach($group as $option)
@continue(! Arr::get($option,'value'))
<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>
<option value="{{ Arr::get($option,'id') }}" @selected(isset($name) && (Arr::get($option,'id') == old($old ?? $name,$value ?? '')))>{{ Arr::get($option,'value') }}</option>
@endforeach
</optgroup>
@endforeach

View File

@ -3,7 +3,7 @@
<!-- $o=Certificate::class -->
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
@foreach($o->tagValuesOld('binary') as $key => $value)
<!-- If this attribute is not handled, it'll be an Attribute::class, we'll just render it normally -->
<!-- If this attribute is not handle, it'll be an Attribute::class, we'll just render it normally -->
@if(($o instanceof Certificate) && $edit)
<input type="hidden" name="name={{ $o->name_lc }}[binary][]" value="{{ md5($value) }}">
@ -16,29 +16,9 @@
@endif
</div>
</div>
<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 class="input-helper">
@lang('Certificate Subject'): <strong>{{ $o->subject($loop->index) }}</strong><br/>
{{ ($expire=$o->expires($loop->index))->isPast() ? __('Expired') : __('Expires') }}: <strong>{{ $expire->format(config('pla.datetime_format','Y-m-d H:i:s')) }}</strong>
</div>
@else

View File

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

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach($server->baseDNs() as $item)
@foreach($server->baseDNs(TRUE)->sort(fn($item)=>$item->sort_key) as $item)
<tr>
<td class="ps-0">{{ $item->getDn() }}</td>
</tr>
@ -44,12 +44,6 @@
<td>Root URL</td>
<td>{{ request()->root() }}</td>
</tr>
<!-- Locale -->
<tr>
<td>Locale</td>
<td>{{ __('locale') }} [{{ config('app.locale') }}]</td>
</tr>
</tbody>
</table>
</div>

View File

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

View File

@ -8,13 +8,13 @@
<div class="d-none round" id="newattr-select">
<div class="row">
<div class="col-12 bg-dark text-light p-2 rounded-2">
<i class="fas fa-plus-circle"></i> @lang('Add New Attribute')
<i class="fas fa-plus-circle"></i> 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>

View File

@ -2,19 +2,19 @@
<div class="col-12 col-xl-3">
<select id="attributetype" class="form-control">
<option value="-all-">-all-</option>
@foreach(($at=$attributetypes->sortBy(fn($item)=>$item->names_lc->join(','))) as $o)
<option value="{{ $o->names_lc->join('-') }}">{{ $o->names->join(',') }}</option>
@foreach($attributetypes as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-xl-9">
@foreach($at as $o)
<span id="at-{{ $o->names_lc->join('-') }}">
@foreach($attributetypes as $o)
<span id="at-{{ $o->name_lc }}">
<table class="schema table table-sm table-bordered table-striped">
<thead>
<tr>
<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>
<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>
</tr>
</thead>
@ -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->get_syntax($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->schemaSyntaxName($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,8 +77,11 @@
<tr>
<td>@lang('Aliases')</td>
<td><strong>
@if($o->names->count() > 1)
{!! $o->names->join('</strong>, <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
@else
@lang('(none)')
@endif

View File

@ -35,10 +35,10 @@
<td>@lang('Inherits from')</td>
<td colspan="3">
<strong>
@if($o->sup_classes->count() === 0)
@if($o->sup->count() === 0)
@lang('(none)')
@else
@foreach($o->sup_classes as $sup)
@foreach($o->sup as $sup)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($sup) }}" href="#{{ strtolower($sup) }}">{{ $sup }}</a>
@endforeach
@ -53,10 +53,10 @@
<strong>
@if(strtolower($o->name) === 'top')
<a class="objectclass" id="-all-">(all)</a>
@elseif(! $o->child_classes->count())
@elseif(! $o->getChildObjectClasses()->count())
@lang('(none)')
@else
@foreach($o->child_classes as $childoc)
@foreach($o->getChildObjectClasses() as $childoc)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($childoc) }}" href="#{{ strtolower($childoc) }}">{{ $childoc }}</a>
@endforeach

View File

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

View File

@ -2,4 +2,10 @@
@section('main-content')
@include('frames.'.$subframe)
@endsection
@endsection
@section('page-scripts')
<script type="text/javascript">
var basedn = {!! $bases->toJson() !!};
</script>
@append

View File

@ -17,72 +17,52 @@
<div class="main-card mb-3 card">
<div class="card-header">
@lang('Create New Entry') - @lang('Step') {{ $step }} @if($template) <span class="ms-auto"><i class="fa fa-fw {{ $template->icon }}"></i> {{ $template->title }}</span>@endif
@lang('Create New Entry') - @lang('Step') {{ $step }}
</div>
<div class="card-body">
<form id="dn-create" method="POST" class="needs-validation" action="{{ url((int)$step === 2 ? 'entry/create' : 'entry/add') }}" enctype="multipart/form-data" novalidate>
@csrf
<input type="hidden" name="_key" value="{{ Crypt::encryptString('*create|'.$container) }}">
<input type="hidden" name="_step" value="{{ $step }}">
<input type="hidden" name="key" value="{{ Crypt::encryptString('*create|'.$container) }}">
<input type="hidden" name="step" value="{{ $step }}">
@switch($step)
@case(1)
<div class="row">
<div class="col-12 col-md-5">
<div class="col-12 col-md-6">
<x-form.select
id="objectclass"
name="objectclass[{{ Entry::TAG_NOTAG }}][]"
old="objectclass.{{ Entry::TAG_NOTAG }}"
:label="__('Select a Structural ObjectClass').'...'"
:label="__('Select a Structural ObjectClass...')"
:options="($oc=$server->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural())
->sortBy(fn($item)=>$item->name_lc)
->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"
:allowclear="true"
multiple="false"
/>
</div>
@if($o->templates->count())
<div class="col-md-1">
<strong>@lang('OR')</strong>
</div>
<div class="col-12 col-md-5">
<x-form.select
name="template"
:label="__('Select a Template').'...'"
:options="$o->templates
->map(fn($item,$key)=>['id'=>$key,'value'=>$item->title])"
:allowclear="true"
/>
</div>
@endif
</div>
@break
@case(2)
<input type="hidden" name="_template" value="{{ $template?->file }}">
<x-attribute-type :o="$o->getObject('rdn')" :edit="true" :new="true" :template="$template" :updated="false"/>
<x-attribute-type :o="$o->getObject('rdn')" :edit="TRUE" :new="FALSE" :updated="FALSE"/>
@foreach($o->getVisibleAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="true" :new="true" :template="$template" :updated="false"/>
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :updated="FALSE"/>
@endforeach
@if(! $template)
<!-- @todo When we come back from validation the javascript to append a new attribute is not loaded -->
<!-- @todo When we render attributes with javascript, the javascript is not loaded -->
@include('fragment.dn.add_attr')
@endif
@include('fragment.dn.add_attr')
@break;
@endswitch
</form>
<div class="row d-none pt-3">
<div class="col-11 {{ $step > 1 ? 'text-end' : '' }} pe-0">
<div class="col-12 {{ $step > 1 ? 'offset-sm-2' : '' }} col-lg-10">
<x-form.reset form="dn-create"/>
<x-form.submit :action="__('Next')" form="dn-create"/>
<x-form.submit action="Next" form="dn-create"/>
</div>
</div>
</div>
@ -92,6 +72,7 @@
@endsection
@section('page-scripts')
<script type="text/javascript">
var rdn_attr;
@ -106,10 +87,6 @@
if (rdn_attr && ($(this)[0].name === rdn_attr+'[]'))
return;
// Exclude attributes marked as noedit
if ($(this).hasClass('noedit'))
return;
$(this).attr('readonly',false);
});
@ -125,15 +102,7 @@
}
$(document).ready(function() {
@if($step === 1)
$('#objectclass').on('select2:open',function(){
$('#template').val(null).trigger('change');
});
$('#template').on('select2:open',function(){
$('#objectclass').val(null).trigger('change');
})
@elseif($step === 2)
@if($step === 2)
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectclass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();

View File

@ -50,6 +50,10 @@
<div class="alert alert-danger p-2">
This entry has [<strong>{!! $x->flatten()->join('</strong>, <strong>') !!}</strong>] tags used by [<strong>{!! $x->keys()->join('</strong>, <strong>') !!}</strong>] that cant be managed by PLA. You can though manage those tags with an LDIF import.
</div>
@elseif(($x=$o->getLangMultiTags())->count())
<div class="alert alert-danger p-2">
This entry has multi-language tags used by [<strong>{!! $x->keys()->join('</strong>, <strong>') !!}</strong>] that cant be managed by PLA. You can though manage those lang tags with an LDIF import.
</div>
@endif
<x-note/>
@ -74,57 +78,61 @@
<div class="col-12">
<div class="d-flex justify-content-center">
<div role="group" class="btn-group btn-group-sm nav pb-3">
<!-- If we have templates that cover this entry -->
@foreach($o->templates as $template)
<span data-bs-toggle="tab" href="#template-{{ $template->name }}" @class(['btn','btn-outline-focus','active'=>$loop->index === 0])><i class="fa fa-fw pe-2 {{ $template->icon }}"></i> {{ $template->title }}</span>
@endforeach
@if($o->templates->count())
<span data-bs-toggle="tab" href="#template-default" @class(['btn','btn-outline-focus','p-1','active'=>(! $o->templates->count())])>{{ __('LDAP Entry') }}</span>
<!-- It is assumed that the entry has atleast 1 template "default" -->
@if($o->templates->count() > 1)
@foreach($o->templates as $template => $name)
<span data-bs-toggle="tab" href="#template-{{$template}}" @class(['btn','btn-outline-focus','active'=>$loop->index === 0])>{{ $name }}</span>
@endforeach
@endif
</div>
</div>
<div class="tab-content">
@foreach($o->templates as $template)
<div @class(['tab-pane','active'=>$loop->index === 0]) id="template-{{ $template->name }}" role="tabpanel">
@include('fragment.template.dn',['template'=>$template])
</div>
@endforeach
@foreach($o->templates as $template => $name)
@switch($template)
@case('default')
<div @class(['tab-pane','active'=>$loop->index === 0]) id="template-{{$template}}" role="tabpanel">
<form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate readonly>
@csrf
<div @class(['tab-pane','active'=>(! $o->templates->count())]) id="template-default" role="tabpanel">
<form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate readonly>
@csrf
<input type="hidden" name="dn" value="">
<input type="hidden" name="dn" value="">
<div class="card-body">
<div class="tab-content">
@php($up=(session()->pull('updated') ?: collect()))
@foreach($o->getVisibleAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :updated="$up->contains($ao->name_lc)"/>
@endforeach
<div class="card-body">
<div class="tab-content">
@php($up=(session()->pull('updated') ?: collect()))
@foreach($o->getVisibleAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="true" :new="false" :template="null" :updated="$up->contains($ao->name)"/>
@endforeach
@include('fragment.dn.add_attr')
@include('fragment.dn.add_attr')
</div>
</div>
</form>
</div>
</div>
</form>
@break
<div class="row d-none pt-3">
<div class="col-11 text-end">
<x-form.reset form="dn-edit"/>
<x-form.submit :action="__('Update')" form="dn-edit"/>
</div>
</div>
</div>
@default
<div @class(['tab-pane','active'=>$loop->index === 0]) id="template-{{$template}}" role="tabpanel">
<p>{{$name}}</p>
</div>
@endswitch
@endforeach
</div>
</div>
</div>
<div class="row d-none pt-3">
<div class="col-12 offset-sm-2 col-sm-4 col-lg-2">
<x-form.reset form="dn-edit"/>
<x-form.submit action="Update" form="dn-edit"/>
</div>
</div>
</div>
<!-- Internal Attributes -->
<div class="tab-pane mt-3" id="internal" role="tabpanel">
@foreach($o->getInternalAttributes() as $ao)
<x-attribute-type :o="$ao" :edit="false" :new="false" :template="$template ?? null" :updated="false"/>
<x-attribute-type :o="$ao" :edit="FALSE" :new="FALSE" :updated="FALSE"/>
@endforeach
</div>
</div>
@ -158,6 +166,7 @@
// Find all input items and turn off readonly
$('input.form-control').each(function() {
// Except for objectClass - @todo show an "X" instead
if ($(this)[0].name.match(/^objectclass/))
return;
@ -282,30 +291,27 @@
})
break;
default:
switch ($(item.relatedTarget).attr('name')) {
case 'entry-userpassword-check':
$.ajax({
method: 'GET',
url: '{{ url('modal/userpassword-check') }}/'+dn,
dataType: 'html',
cache: false,
beforeSend: function() {
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
},
success: function(data) {
that.empty().html(data);
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
break;
case 'entry-userpassword-check':
$.ajax({
method: 'GET',
url: '{{ url('modal/userpassword-check') }}/'+dn,
dataType: 'html',
cache: false,
beforeSend: function() {
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
},
success: function(data) {
that.empty().html(data);
},
error: function(e) {
if (e.status !== 412)
alert('That didnt work? Please try again....');
},
})
break;
default:
console.log('No action for button:'+$(item.relatedTarget).attr('id'));
}
default:
console.log('No action for button:'+$(item.relatedTarget).attr('id'));
}
});

View File

@ -15,7 +15,7 @@
<div class="main-card mb-3 card">
<form id="import-form" action="{{ url('import/process/ldif') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="hidden" name="_key" value="{{ Crypt::encryptString('*import|_NOP') }}">
<input type="hidden" name="key" value="{{ Crypt::encryptString('*import|_NOP') }}">
<div class="card-header">
@lang('LDIF Import')
@ -56,7 +56,7 @@
<div class="card-footer">
<span class="ms-auto">
<x-form.submit :action="__('Process')" form="import-form"/>
<x-form.submit action="Process" form="import-form"/>
</span>
</div>
</div>

View File

@ -21,11 +21,7 @@
: $attribute !!}
</th>
<td>
@if($ao instanceof \App\Classes\LDAP\Attribute\Schema\OID)
<x-attribute :edit="false" :o="$ao"/>
@else
{!! $ao->values_old->dot()->join('<br>') !!}
@endif
<x-attribute :edit="false" :o="$ao"/>
</td>
</tr>
@endforeach

View File

@ -1,5 +1,16 @@
@extends('architect::layouts.app')
{{--
@section('htmlheader_title')
@lang('Home')
@endsection
@section('page_title')
@endsection
@section('page_icon')
@endsection
--}}
@section('main-content')
<x-success/>
@ -44,12 +55,6 @@
<li class="ps-0 p-1">
<i class="fas fa-fw fa-sitemap me-2"></i> Hierarchical Tree View
</li>
<li class="ps-0 p-1">
<i class="fas fa-fw fa-clone me-2"></i> Creation and Modification Templates
</li>
<li class="ps-0 p-1">
<i class="fas fa-fw fa-magnifying-glass-chart me-2"></i> Data Rich Attribute Values
</li>
<li class="ps-0 p-1">
<i class="fas fa-fw fa-language me-2"></i> Multi-language Support
</li>
@ -73,6 +78,8 @@
@section('page-scripts')
<script type="text/javascript">
var basedn = {!! $bases->toJson() !!};
var subpage = window.location.hash;
$(document).ready(function() {

View File

@ -64,7 +64,7 @@
<div class="row p-3">
<div class="col-12 offset-sm-2 col-sm-4 col-lg-2 mx-auto">
<x-form.cancel/>
<x-form.submit :action="__('Update')" form="dn-update"/>
<x-form.submit action="Update" form="dn-update"/>
</div>
</div>

View File

@ -1,18 +0,0 @@
{
"title": "Generic: DNS Entry",
"description": "New DNS Entry",
"enabled": true,
"icon": "fa-globe",
"rdn": "dc",
"objectclasses": [
"dnsDomain"
],
"attributes": {
"dc": {
"display": "Domain Component",
"order": 1
}
}
}

View File

@ -1,42 +0,0 @@
{
"title": "Example entry", // Title shown when selecting tempaltes
"description": "This is the description", // Unused, only for documenting
"enabled": false, // Whether template is enabled or not
"icon": "fa-star-of-life", // Icon shown when rendering an existing entry that identifies as this template
"rdn": "o", // @todo not implemented
"regexp": "/^$/", // Regular expression that restricts where this template cna be used
"objectclasses": [ // Objectclasses that entries will have if they use this template
"organization"
],
"attributes": { // Attribute configuration
"attribute1": { // LDAP attribute name
"display": "Attribute 1", // Displayed when accepting input for this value
"hint": "This is an example", // @todo not implemented
"type": null, // Default is NULL, so use normal Attribute rendering type
"order": 1 // Order to show attributes
},
"attribute2": {
"display": "Attribute 2",
"hint": "This is an example",
"type": "input", // Force attribute to use template input
"order": 2
},
"attribute3": {
"display": "Attribute 3",
"type": "select", // Force attribute to use template select
"options": { // Select options
"/bin/bash": "Bash",
"/bin/csh": "C Shell",
"/bin/dash": "Dash",
"/bin/sh": "Shell",
"/bin/tsh": "Turbo C Shell",
"/bin/zsh": "ZSH",
"/bin/false": "False",
"/usr/sbin/nologin": "No Login"
},
"order": 8
}
}
}

View File

@ -1,20 +0,0 @@
{
"title": "Generic: Organisational",
"description": "New Organisational",
"enabled": true,
"icon": "fa-building",
"rdn": "ou",
"regexp": "/^c=.+,?/",
"objectclasses": [
"organization"
],
"attributes": {
"o": {
"display": "Organisation",
"hint": "This is an example",
"order": 1
}
}
}

View File

@ -1,20 +0,0 @@
{
"title": "Generic: Organisational Unit",
"description": "New Organisational Unit",
"enabled": true,
"icon": "fa-layer-group",
"rdn": "ou",
"regexp": "/^o=.+,?/",
"objectclasses": [
"organizationalUnit"
],
"attributes": {
"ou": {
"display": "Organisational Unit",
"hint": "This is an example",
"order": 1
}
}
}

View File

@ -1,81 +0,0 @@
{
"title": "Generic: User Account",
"description": "New User Account",
"enabled": true,
"icon": "fa-user",
"rdn": "cn",
"regexp": "/^ou=.+,?/",
"objectclasses": [
"inetOrgPerson",
"posixAccount"
],
"attributes": {
"givenName": {
"display": "First Name",
"onchange": [
"=autoFill(cn;%givenName% %sn/U%)",
"=autoFill(uid;%givenName|0-1/l%%sn/l%)"
],
"order": 1
},
"sn": {
"display": "Last Name",
"onchange": [
"=autoFill(cn;%givenName% %sn/U%)",
"=autoFill(uid;%givenName|0-1/l%%sn/l%)"
],
"order": 2
},
"cn": {
"display": "Common Name",
"readonly": true,
"order": 3
},
"uid": {
"display": "User ID",
"onchange": [
"=autoFill(homeDirectory;/home/users/%uid%)"
],
"order": 4
},
"userPassword": {
"display": "Password",
"order": 5
},
"uidNumber": {
"display": "UID Number",
"readonly": true,
"value": "=getNextNumber(/;uidNumber)",
"order": 6
},
"gidNumber": {
"display": "GID Number",
"onchange": [
"=autoFill(homeDirectory;/home/users/%gidNumber|0-0/T%/%uid|3-%)"
],
"value": "=getNextNumber(/;gidNumber)",
"order": 7
},
"homeDirectory": {
"display": "Home Directory",
"order": 8
},
"loginShell": {
"display": "Login Shell",
"type": "select",
"options": {
"/bin/bash": "Bash",
"/bin/csh": "C Shell",
"/bin/dash": "Dash",
"/bin/sh": "Shell",
"/bin/tsh": "Turbo C Shell",
"/bin/zsh": "ZSH",
"/bin/false": "False",
"/usr/sbin/nologin": "No Login"
},
"order": 9
}
}
}

View File

@ -34,7 +34,7 @@ class ImportTest extends TestCase
->from('/import')
->post('/import/process/ldif',[
'_token' => csrf_token(),
'_key'=>Crypt::encryptString('*import|_NOP'),
'key'=>Crypt::encryptString('*import|_NOP'),
'file' => $file,
]);

View File

@ -99,7 +99,10 @@ jpegphoto:: /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkS
Q05bKyt9PtEtrWPZEuSBkkkk5JJPJJJJJPJJoopOc5xu43SZGyMWYLDuXRRRRTU9f//Z
l: Springfield
mail: bart.simpson@simpsons.example.com
mailHost: simpsons.host
mailRoutingAddress: bsimpson@simpsons.example.com
o: The Simpsons
objectclass: inetLocalMailRecipient
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top

View File

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

View File

@ -1,4 +0,0 @@
dn: cn=z-module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: sssvlv

View File

@ -1,8 +0,0 @@
dn: olcOverlay=sssvlv,olcDatabase={4}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcSssVlvConfig
olcOverlay: sssvlv
olcSssVlvMax: 1
olcSssVlvMaxKeys: 5
olcSssVlvMaxPerConn: 5

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