diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index a8d013b5..c757adb8 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -7,6 +7,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use App\Classes\LDAP\Schema\AttributeType; +use App\Exceptions\InvalidUsage; use App\Ldap\Entry; /** @@ -14,9 +15,6 @@ use App\Ldap\Entry; */ class Attribute implements \Countable, \ArrayAccess { - // Attribute Name - protected string $name; - // Is this attribute an internal attribute protected ?bool $_is_internal = NULL; protected(set) bool $no_attr_tags = FALSE; @@ -98,11 +96,11 @@ class Attribute implements \Countable, \ArrayAccess * @param string $name Name of the attribute * @param array $values Current Values * @param array $oc The objectclasses that the DN of this attribute has + * @throws InvalidUsage */ public function __construct(string $dn,string $name,array $values,array $oc=[]) { $this->dn = $dn; - $this->name = $name; $this->_values = collect($values); $this->_values_old = collect($values); @@ -113,8 +111,12 @@ class Attribute implements \Countable, \ArrayAccess // Get the objectclass heirarchy for required attribute determination foreach ($oc as $objectclass) { - $this->oc->push($objectclass); - $this->oc = $this->oc->merge(config('server')->schema('objectclasses',$objectclass)->getParents()->pluck('name')); + $soc = config('server')->schema('objectclasses',$objectclass); + + if ($soc) { + $this->oc->push($soc->oid); + $this->oc = $this->oc->merge($soc->getParents()->pluck('oid')); + } } /* @@ -140,8 +142,6 @@ 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 @@ -164,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 ? $this->schema->{$key} : $this->{$key}, + 'name' => $this->schema->{$key}, // Attribute name in lower case 'name_lc' => strtolower($this->name), // Required by Object Classes @@ -268,9 +268,6 @@ class Attribute implements \Countable, \ArrayAccess if ($this->is_rdn) $result->put(__('rdn'),__('This attribute is required for the RDN')); - // If this attribute name is an alias for the schema attribute name - // @todo - if ($this->required()->count()) $result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', '))); @@ -302,7 +299,7 @@ class Attribute implements \Countable, \ArrayAccess { return $this->schema->used_in_object_classes ->keys() - ->intersect($this->schema->heirachy($this->oc)) + ->intersect($this->oc) ->count() === 0; } @@ -329,6 +326,7 @@ 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 string|null $template * @return View */ public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,bool $updated=FALSE,?string $template=NULL): View diff --git a/app/Classes/LDAP/Attribute/Factory.php b/app/Classes/LDAP/Attribute/Factory.php index 615fdf95..a4ee42e4 100644 --- a/app/Classes/LDAP/Attribute/Factory.php +++ b/app/Classes/LDAP/Attribute/Factory.php @@ -59,8 +59,6 @@ class Factory public static function create(string $dn,string $attribute,array $values,array $oc=[]): Attribute { $class = Arr::get(self::map,strtolower($attribute),Attribute::class); - Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class)); - return new $class($dn,$attribute,$values,$oc); } } \ No newline at end of file diff --git a/app/Classes/LDAP/Schema/AttributeType.php b/app/Classes/LDAP/Schema/AttributeType.php index cd397069..5706a6a7 100644 --- a/app/Classes/LDAP/Schema/AttributeType.php +++ b/app/Classes/LDAP/Schema/AttributeType.php @@ -7,304 +7,74 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use App\Classes\LDAP\Attribute; +use App\Exceptions\InvalidUsage; use App\Ldap\Entry; /** * Represents an LDAP AttributeType - * - * @package phpLDAPadmin - * @subpackage Schema */ -final class AttributeType extends Base { - // The attribute from which this attribute inherits (if any) - private ?string $sup_attribute = NULL; +final class AttributeType extends Base +{ + private const LOGKEY = 'SAT'; - // Array of AttributeTypes which inherit from this one - private Collection $children; + // An array of AttributeTypes which inherit from this one + private(set) Collection $children; // The equality rule used - private ?string $equality = NULL; - - // The ordering of the attributeType - private ?string $ordering = NULL; - - // Supports substring matching? - private ?string $sub_str_rule = NULL; - - // The full syntax string, ie 1.2.3.4{16} - private ?string $syntax = NULL; - private ?string $syntax_oid = NULL; - - // boolean: is single valued only? - private bool $is_single_value = FALSE; - - // boolean: is collective? - private bool $is_collective = FALSE; - - // boolean: can use modify? - private bool $is_no_user_modification = FALSE; - - // The usage string set by the LDAP schema - private ?string $usage = NULL; - - // An array of alias attribute names, strings - private Collection $aliases; - - // The max number of characters this attribute can be - private ?int $max_length = NULL; - - // A string description of the syntax type (taken from the LDAPSyntaxes) - /** - * @deprecated - reference syntaxes directly if possible - * @var string - */ - private ?string $type = NULL; - - // An array of objectClasses which use this attributeType (must be set by caller) - private Collection $used_in_object_classes; - - // A list of object class names that require this attribute type. - private Collection $required_by_object_classes; + private(set) ?string $equality = NULL; // This attribute has been forced a MAY attribute by the configuration. - private bool $forced_as_may = FALSE; + private(set) bool $forced_as_may = FALSE; - /** - * Creates a new AttributeType object from a raw LDAP AttributeType string. - * - * eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) - */ - public function __construct(string $line) { - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('Parsing AttributeType [%s]',$line)); + // boolean: is collective? + private(set) bool $is_collective = FALSE; - parent::__construct($line); + // Is this a must attribute + private(set) bool $is_must = FALSE; - $strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE); + // boolean: can use modify? + private(set) bool $is_no_user_modification = FALSE; - // Init - $this->children = collect(); - $this->aliases = collect(); - $this->used_in_object_classes = collect(); - $this->required_by_object_classes = collect(); + // boolean: is single valued only? + private(set) bool $is_single_value = FALSE; - for ($i=0; $i < count($strings); $i++) { - switch ($strings[$i]) { - case '(': - case ')': - break; + // The max number of characters this attribute can be + private(set) ?int $max_length = NULL; - case 'NAME': - // @note Some schema's return a (' instead of a ( ' - if ($strings[$i+1] != '(' && ! preg_match('/^\(/',$strings[$i+1])) { - do { - $this->name .= ($this->name ? ' ' : '').$strings[++$i]; + // An array of names (including aliases) that this attribute is known by + private(set) Collection $names; - } while (! preg_match("/\'$/s",$strings[$i])); + // The ordering of the attributeType + private(set) ?string $ordering = NULL; - // This attribute has no aliases - //$this->aliases = collect(); + // A list of object class names that require this attribute type. + private(set) Collection $required_by_object_classes; - } else { - $i++; + // Which objectclass is defining this attribute for an Entry + public ?string $source = NULL; - do { - // In case we came here becaues of a (' - if (preg_match('/^\(/',$strings[$i])) - $strings[$i] = preg_replace('/^\(/','',$strings[$i]); - else - $i++; + // Supports substring matching? + private(set) ?string $sub_str_rule = NULL; - $this->name .= ($this->name ? ' ' : '').$strings[++$i]; + // The attribute from which this attribute inherits (if any) + private(set) ?string $sup_attribute = NULL; - } while (! preg_match("/\'$/s",$strings[$i])); + // The full syntax string, ie 1.2.3.4{16} + private(set) ?string $syntax = NULL; + private(set) ?string $syntax_oid = NULL; - // Add alias names for this attribute - while ($strings[++$i] != ')') { - $alias = $strings[$i]; - $alias = preg_replace("/^\'(.*)\'$/",'$1',$alias); - $this->addAlias($alias); - } - } + // The usage string set by the LDAP schema + private(set) ?string $usage = NULL; - $this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name); - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case NAME returned (%s)',$this->name),['aliases'=>$this->aliases]); - break; - - case 'DESC': - do { - $this->description .= ($this->description ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - $this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description); - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case DESC returned (%s)',$this->description)); - break; - - case 'OBSOLETE': - $this->is_obsolete = TRUE; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete)); - break; - - case 'SUP': - $i++; - $this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]); - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_attribute)); - break; - - case 'EQUALITY': - $this->equality = $strings[++$i]; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case EQUALITY returned (%s)',$this->equality)); - break; - - case 'ORDERING': - $this->ordering = $strings[++$i]; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case ORDERING returned (%s)',$this->ordering)); - break; - - case 'SUBSTR': - $this->sub_str_rule = $strings[++$i]; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case SUBSTR returned (%s)',$this->sub_str_rule)); - break; - - case 'SYNTAX': - $this->syntax = $strings[++$i]; - $this->syntax_oid = preg_replace('/{\d+}$/','',$this->syntax); - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('/ Evaluating SYNTAX returned (%s) [%s]',$this->syntax,$this->syntax_oid)); - - // Does this SYNTAX string specify a max length (ie, 1.2.3.4{16}) - $m = []; - if (preg_match('/{(\d+)}$/',$this->syntax,$m)) - $this->max_length = $m[1]; - else - $this->max_length = NULL; - - if ($i < count($strings) - 1 && $strings[$i+1] == '{') - do { - $this->name .= ' '.$strings[++$i]; - } while ($strings[$i] != '}'); - - $this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax); - $this->syntax_oid = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax_oid); - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case SYNTAX returned (%s) [%s] {%d}',$this->syntax,$this->syntax_oid,$this->max_length)); - break; - - case 'SINGLE-VALUE': - $this->is_single_value = TRUE; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case SINGLE-VALUE returned (%s)',$this->is_single_value)); - break; - - case 'COLLECTIVE': - $this->is_collective = TRUE; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case COLLECTIVE returned (%s)',$this->is_collective)); - break; - - case 'NO-USER-MODIFICATION': - $this->is_no_user_modification = TRUE; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case NO-USER-MODIFICATION returned (%s)',$this->is_no_user_modification)); - break; - - case 'USAGE': - $this->usage = $strings[++$i]; - - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case USAGE returned (%s)',$this->usage)); - break; - - // @note currently not captured - case 'X-ORDERED': - if (static::DEBUG_VERBOSE) - Log::error(sprintf('- Case X-ORDERED returned (%s)',$strings[++$i])); - break; - - // @note currently not captured - case 'X-ORIGIN': - $value = ''; - - do { - $value .= ($value ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - if (static::DEBUG_VERBOSE) - Log::error(sprintf('- Case X-ORIGIN returned (%s)',$value)); - break; - - default: - if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) { - $this->oid = $strings[$i]; - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('- Case default returned (%s)',$this->oid)); - - } elseif ($strings[$i]) - Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]); - } - } - } - - public function __clone() - { - // When we clone, we need to break the reference too - $this->aliases = clone $this->aliases; - } + // An array of objectClasses which use this attributeType (must be set by caller) + private(set) Collection $used_in_object_classes; public function __get(string $key): mixed { - switch ($key) { - case 'aliases': return $this->aliases; - case 'children': return $this->children; - case 'forced_as_may': return $this->forced_as_may; - case 'is_collective': return $this->is_collective; - case 'is_editable': return ! $this->is_no_user_modification; - case 'is_no_user_modification': return $this->is_no_user_modification; - case 'is_single_value': return $this->is_single_value; - case 'equality': return $this->equality; - case 'max_length': return $this->max_length; - case 'ordering': return $this->ordering; - case 'required_by_object_classes': return $this->required_by_object_classes; - case 'sub_str_rule': return $this->sub_str_rule; - case 'sup_attribute': return $this->sup_attribute; - case 'syntax': return $this->syntax; - case 'syntax_oid': return $this->syntax_oid; - case 'type': return $this->type; - case 'usage': return $this->usage; - case 'used_in_object_classes': return $this->used_in_object_classes; - - default: return parent::__get($key); - } - } - - /** - * Adds an attribute name to the alias array. - * - * @param string $alias The name of a new attribute to add to this attribute's list of aliases. - */ - public function addAlias(string $alias): void - { - $this->aliases->push($alias); + return match ($key) { + 'names_lc' => $this->names->map('strtolower'), + default => parent::__get($key) + }; } /** @@ -315,7 +85,8 @@ final class AttributeType extends Base { */ public function addChild(string $child): void { - $this->children->push($child); + $this->children + ->push($child); } /** @@ -346,144 +117,10 @@ final class AttributeType extends Base { private function factory(): Attribute { - return Attribute\Factory::create(dn:'',attribute:$this->name,values:[]); - } - - /** - * Gets the names of attributes that are an alias for this attribute (if any). - * - * @return Collection An array of names of attributes which alias this attribute or - * an empty array if no attribute aliases this object. - * @deprecated use class->aliases - */ - public function getAliases(): Collection - { - return $this->aliases; - } - - /** - * Gets this attribute's equality string - * - * @return string - * @deprecated use $this->equality - */ - public function getEquality() - { - return $this->equality; - } - - /** - * Gets whether this attribute is collective. - * - * @return boolean Returns TRUE if this attribute is collective and FALSE otherwise. - * @deprecated use $this->is_collective - */ - public function getIsCollective(): bool - { - return $this->is_collective; - } - - /** - * Gets whether this attribute is not modifiable by users. - * - * @return boolean Returns TRUE if this attribute is not modifiable by users. - * @deprecated use $this->is_no_user_modification - */ - public function getIsNoUserModification(): bool - { - return $this->is_no_user_modification; - } - - /** - * Gets whether this attribute is single-valued. If this attribute only supports single values, TRUE - * is returned. If this attribute supports multiple values, FALSE is returned. - * - * @return boolean Returns TRUE if this attribute is single-valued or FALSE otherwise. - * @deprecated use class->is_single_value - */ - public function getIsSingleValue(): bool - { - return $this->is_single_value; - } - - /** - * Gets this attribute's the maximum length. If no maximum is defined by the LDAP server, NULL is returned. - * - * @return int The maximum length (in characters) of this attribute or NULL if no maximum is specified. - * @deprecated use $this->max_length; - */ - public function getMaxLength() - { - return $this->max_length; - } - - /** - * Gets this attribute's ordering specification. - * - * @return string - * @deprecated use $this->ordering - */ - public function getOrdering(): string - { - return $this->ordering; - } - - /** - * Gets this attribute's substring matching specification - * - * @return string - * @deprecated use $this->sub_str_rule; - */ - public function getSubstr() { - return $this->sub_str_rule; - } - - /** - * Gets this attribute's parent attribute (if any). If this attribute does not - * inherit from another attribute, NULL is returned. - * - * @return string - * @deprecated use $class->sup_attribute directly - */ - public function getSupAttribute() { - return $this->sup_attribute; - } - - /** - * Gets this attribute's syntax OID. Differs from getSyntaxString() in that this - * function only returns the actual OID with any length specification removed. - * Ie, if the syntax string is "1.2.3.4{16}", this function only retruns - * "1.2.3.4". - * - * @return string The syntax OID string. - * @deprecated use $this->syntax_oid; - */ - public function getSyntaxOID() - { - return $this->syntax_oid; - } - - /** - * Gets this attribute's usage string as defined by the LDAP server - * - * @return string - * @deprecated use $this->usage - */ - public function getUsage() - { - return $this->usage; - } - - /** - * Gets the list of "used in" objectClasses, that is the list of objectClasses - * which provide this attribute. - * - * @return Collection An array of names of objectclasses (strings) which provide this attribute - * @deprecated use $this->used_in_object_classes - */ - public function getUsedInObjectClasses(): Collection - { - return $this->used_in_object_classes; + return Attribute\Factory::create( + dn:'', + attribute:$this->name, + values:[]); } /** @@ -492,58 +129,162 @@ final class AttributeType extends Base { * @param Collection $ocs * @return Collection */ - public function heirachy(Collection $ocs): Collection + private function heirachy(Collection $ocs): Collection { $result = collect(); foreach ($ocs as $oc) { - $schema = config('server') - ->schema('objectclasses',$oc) - ->getParents(TRUE) - ->pluck('name'); + $item = config('server') + ->schema('objectclasses',$oc); - $result = $result->merge($schema)->push($oc); + $result = $result + ->merge($item + ->getParents(TRUE) + ->pluck('oid')) + ->push($item->oid); } return $result; } /** - * @return bool - * @deprecated use $this->forced_as_may - */ - public function isForceMay(): bool - { - return $this->forced_as_may; - } - - /** - * Removes an attribute name from this attribute's alias array. + * Creates a new AttributeType object from a raw LDAP AttributeType string. * - * @param string $alias The name of the attribute to remove. + * eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) */ - public function removeAlias(string $alias): void + protected function parse(string $line): void { - if (($x=$this->aliases->search($alias)) !== FALSE) - $this->aliases->forget($x); + Log::debug(sprintf('%s:Parsing AttributeType [%s]',self::LOGKEY,$line)); + + // Init + $this->names = collect(); + $this->children = collect(); + $this->used_in_object_classes = collect(); + $this->required_by_object_classes = collect(); + + parent::parse($line); } - /** - * Sets this attribute's list of aliases. - * - * @param Collection $aliases The array of alias names (strings) - * @deprecated use $this->aliases = - */ - public function setAliases(Collection $aliases): void + protected function parse_chunk(array $strings,int &$i): void { - $this->aliases = $aliases; - } + switch ($strings[$i]) { + case 'NAME': + $name = ''; - /** - * This function will mark this attribute as a forced MAY attribute - */ - public function setForceMay() { - $this->forced_as_may = TRUE; + // @note Some schema's return a (' instead of a ( ' + // @note This attribute format has no aliases + if ($strings[$i+1] !== '(' && ! preg_match('/^\(/',$strings[$i+1])) { + do { + $name .= ($name ? ' ' : '').$strings[++$i]; + + } while (! preg_match("/\'$/s",$strings[$i])); + + } else { + $i++; + + do { + // In case we came here because of a (' + if (preg_match('/^\(/',$strings[$i])) + $strings[$i] = preg_replace('/^\(/','',$strings[$i]); + else + $i++; + + $name .= ($name ? ' ' : '').$strings[++$i]; + + } while (! preg_match("/\'$/s",$strings[$i])); + + // Add alias names for this attribute + while ($strings[++$i] !== ')') { + $alias = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]); + $this->names->push($alias); + } + } + + $this->names = $this->names->push(preg_replace("/^\'(.*)\'$/",'$1',$name))->sort(); + $this->forced_as_may = $this->names_lc + ->intersect(array_map('strtolower',config('pla.force_may',[]))) + ->count() > 0; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case NAME returned (%s)',self::LOGKEY,$this->name),['names'=>$this->names]); + break; + + case 'SUP': + $this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[++$i]); + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case SUP returned (%s)',self::LOGKEY,$this->sup_attribute)); + break; + + case 'EQUALITY': + $this->equality = $strings[++$i]; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case EQUALITY returned (%s)',self::LOGKEY,$this->equality)); + break; + + case 'ORDERING': + $this->ordering = $strings[++$i]; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case ORDERING returned (%s)',self::LOGKEY,$this->ordering)); + break; + + case 'SUBSTR': + $this->sub_str_rule = $strings[++$i]; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case SUBSTR returned (%s)',self::LOGKEY,$this->sub_str_rule)); + break; + + case 'SYNTAX': + $this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$strings[++$i]); + $this->syntax_oid = preg_replace("/^\'?(.*){\d+}\'?$/",'$1',$this->syntax); + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:/ Evaluating SYNTAX returned (%s) [%s]',self::LOGKEY,$this->syntax,$this->syntax_oid)); + + // Does this SYNTAX string specify a max length (ie, 1.2.3.4{16}) + $m = []; + $this->max_length = preg_match('/{(\d+)}$/',$this->syntax,$m) + ? $m[1] + : NULL; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case SYNTAX returned (%s) [%s] {%d}',self::LOGKEY,$this->syntax,$this->syntax_oid,$this->max_length)); + break; + + case 'SINGLE-VALUE': + $this->is_single_value = TRUE; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case SINGLE-VALUE returned (%s)',self::LOGKEY,$this->is_single_value)); + break; + + case 'COLLECTIVE': + $this->is_collective = TRUE; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case COLLECTIVE returned (%s)',self::LOGKEY,$this->is_collective)); + break; + + case 'NO-USER-MODIFICATION': + $this->is_no_user_modification = TRUE; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case NO-USER-MODIFICATION returned (%s)',self::LOGKEY,$this->is_no_user_modification)); + break; + + case 'USAGE': + $this->usage = $strings[++$i]; + + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case USAGE returned (%s)',self::LOGKEY,$this->usage)); + break; + + default: + parent::parse_chunk($strings,$i); + } } /** @@ -557,13 +298,28 @@ final class AttributeType extends Base { } /** - * Sets this attribute's SUP attribute (ie, the attribute from which this attribute inherits). + * If this is a MUST attribute to the objectclass that defines it * - * @param string $attr The name of the new parent (SUP) attribute + * @return void */ - public function setSupAttribute(string $attr): void + public function setMust(): void { - $this->sup_attribute = trim($attr); + $this->is_must = TRUE; + } + + /** + * Sets this attribute's name. + * + * @param string $name The new name to give this attribute. + * @throws InvalidUsage + */ + public function setName(string $name): void + { + // Quick validation + if ($this->names_lc->count() && (! $this->names_lc->contains(strtolower($name)))) + throw new InvalidUsage(sprintf('Cannot set attribute name to [%s], its not an alias for [%s]',$name,$this->names->join(','))); + + $this->name = $name; } /** diff --git a/app/Classes/LDAP/Schema/Base.php b/app/Classes/LDAP/Schema/Base.php index b53e1dca..b5e38321 100644 --- a/app/Classes/LDAP/Schema/Base.php +++ b/app/Classes/LDAP/Schema/Base.php @@ -57,7 +57,7 @@ abstract class Base public function __toString(): string { - return $this->name; + return $this->oid; } protected function parse(string $line): void diff --git a/app/Classes/LDAP/Schema/LDAPSyntax.php b/app/Classes/LDAP/Schema/LDAPSyntax.php index 0891a563..33098320 100644 --- a/app/Classes/LDAP/Schema/LDAPSyntax.php +++ b/app/Classes/LDAP/Schema/LDAPSyntax.php @@ -6,74 +6,49 @@ use Illuminate\Support\Facades\Log; /** * Represents an LDAP Syntax - * - * @package phpLDAPadmin - * @subpackage Schema */ -final class LDAPSyntax extends Base { +final class LDAPSyntax extends Base +{ + private const LOGKEY = 'SLS'; + // Is human readable? - private ?bool $is_not_human_readable = NULL; + private(set) ?bool $is_not_human_readable = NULL; // Binary transfer required? - private ?bool $binary_transfer_required = NULL; + private(set) ?bool $binary_transfer_required = NULL; /** * Creates a new Syntax object from a raw LDAP syntax string. */ - public function __construct(string $line) { - Log::debug(sprintf('Parsing LDAPSyntax [%s]',$line)); + protected function parse(string $line): void + { + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:Parsing LDAPSyntax [%s]',self::LOGKEY,$line)); - parent::__construct($line); - - $strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE); + parent::parse($line); + } + protected function parse_chunk(array $strings,int &$i): void + { for ($i=0; $idescription .= (strlen($this->description) ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - $this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description); - - Log::debug(sprintf('- Case DESC returned (%s)',$this->description)); - break; - case 'X-BINARY-TRANSFER-REQUIRED': $this->binary_transfer_required = (str_replace("'",'',$strings[++$i]) === 'TRUE'); - Log::debug(sprintf('- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',$this->binary_transfer_required)); + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',self::LOGKEY,$this->binary_transfer_required)); break; case 'X-NOT-HUMAN-READABLE': $this->is_not_human_readable = (str_replace("'",'',$strings[++$i]) === 'TRUE'); - Log::debug(sprintf('- Case X-NOT-HUMAN-READABLE returned (%s)',$this->is_not_human_readable)); + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:- Case X-NOT-HUMAN-READABLE returned (%s)',self::LOGKEY,$this->is_not_human_readable)); break; default: - if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) { - $this->oid = $strings[$i]; - Log::debug(sprintf('- Case default returned (%s)',$this->oid)); - - } elseif ($strings[$i]) - Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]); + parent::parse_chunk($strings,$i); } } } - - public function __get(string $key): mixed - { - switch ($key) { - case 'binary_transfer_required': return $this->binary_transfer_required; - case 'is_not_human_readable': return $this->is_not_human_readable; - - default: return parent::__get($key); - } - } } \ No newline at end of file diff --git a/app/Classes/LDAP/Schema/MatchingRule.php b/app/Classes/LDAP/Schema/MatchingRule.php index 876e5b36..48737768 100644 --- a/app/Classes/LDAP/Schema/MatchingRule.php +++ b/app/Classes/LDAP/Schema/MatchingRule.php @@ -7,106 +7,16 @@ use Illuminate\Support\Facades\Log; /** * Represents an LDAP MatchingRule - * - * @package phpLDAPadmin - * @subpackage Schema */ -final class MatchingRule extends Base { +final class MatchingRule extends Base +{ + private const LOGKEY = 'SMR'; + // This rule's syntax OID - private ?string $syntax = NULL; + private(set) ?string $syntax = NULL; // An array of attribute names who use this MatchingRule - private Collection $used_by_attrs; - - /** - * Creates a new MatchingRule object from a raw LDAP MatchingRule string. - */ - function __construct(string $line) { - Log::debug(sprintf('Parsing MatchingRule [%s]',$line)); - - parent::__construct($line); - - $strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE); - - // Init - $this->used_by_attrs = collect(); - - for ($i=0; $iname .= (strlen($this->name) ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - } else { - $i++; - - do { - $this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - do { - $i++; - - } while (! preg_match('/\)+\)?/',$strings[$i])); - } - - $this->name = preg_replace("/^\'/",'',$this->name); - $this->name = preg_replace("/\'$/",'',$this->name); - - Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name))); - break; - - case 'DESC': - do { - $this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i]; - - } while (! preg_match("/\'$/s",$strings[$i])); - - $this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description); - - Log::debug(sprintf('- Case DESC returned (%s)',$this->description)); - break; - - case 'OBSOLETE': - $this->is_obsolete = TRUE; - - Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete)); - break; - - case 'SYNTAX': - $this->syntax = $strings[++$i]; - - Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax)); - break; - - default: - if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) { - $this->oid = $strings[$i]; - Log::debug(sprintf('- Case default returned (%s)',$this->oid)); - - } elseif ($strings[$i]) - Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]); - } - } - } - - public function __get(string $key): mixed - { - switch ($key) { - case 'syntax': return $this->syntax; - case 'used_by_attrs': return $this->used_by_attrs; - - default: return parent::__get($key); - } - } + private(set) Collection $used_by_attrs; /** * Adds an attribute name to the list of attributes who use this MatchingRule @@ -120,23 +30,33 @@ final class MatchingRule extends Base { } /** - * Gets an array of attribute names (strings) which use this MatchingRule + * Creates a new MatchingRule object from a raw LDAP MatchingRule string. * - * @return array The array of attribute names (strings). - * @deprecated use $this->used_by_attrs + * @param string $line + * @return void */ - public function getUsedByAttrs() + protected function parse(string $line): void { - return $this->used_by_attrs; + if (static::DEBUG_VERBOSE) + Log::debug(sprintf('%s:Parsing MatchingRule [%s]',self::LOGKEY,$line)); + + // Init + $this->used_by_attrs = collect(); + + parent::parse($line); } - /** - * Sets the list of used_by_attrs to the array specified by $attrs; - * - * @param Collection $attrs The array of attribute names (strings) which use this MatchingRule - */ - public function setUsedByAttrs(Collection $attrs): void + protected function parse_chunk(array $strings,int &$i): void { - $this->used_by_attrs = $attrs; + switch ($strings[$i]) { + case 'SYNTAX': + $this->syntax = $strings[++$i]; + + Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax)); + break; + + default: + parent::parse_chunk($strings,$i); + } } } diff --git a/app/Classes/LDAP/Schema/ObjectClass.php b/app/Classes/LDAP/Schema/ObjectClass.php index 3b6b8d0e..8fb235e5 100644 --- a/app/Classes/LDAP/Schema/ObjectClass.php +++ b/app/Classes/LDAP/Schema/ObjectClass.php @@ -10,9 +10,6 @@ use App\Exceptions\InvalidUsage; /** * Represents an LDAP Schema objectClass - * - * @package phpLDAPadmin - * @subpackage Schema */ final class ObjectClass extends Base { @@ -27,21 +24,14 @@ final class ObjectClass extends Base // One of STRUCTURAL, ABSTRACT, or AUXILIARY private int $type; - // Arrays of attribute names that this objectClass requires - private Collection $must_attrs; - - // Arrays of attribute names that this objectClass allows, but does not require - private Collection $may_attrs; - - // Arrays of attribute names that this objectClass has been forced to MAY attrs, due to configuration - private(set) Collection $may_force; - - private bool $is_obsolete; + // Attributes that this objectclass defines + private(set) Collection $attributes; public function __get(string $key): mixed { return match ($key) { - 'attributes' => $this->getAllAttrs(TRUE), + 'all_attributes' => $this->getMustAttrs(TRUE) + ->merge($this->getMayAttrs(TRUE)), 'type_name' => match ($this->type) { Server::OC_STRUCTURAL => 'Structural', Server::OC_ABSTRACT => 'Abstract', @@ -52,20 +42,6 @@ final class ObjectClass extends Base }; } - /** - * Return a list of attributes that this objectClass provides - * - * @param bool $parents - * @return Collection - * @throws InvalidUsage - */ - public function getAllAttrs(bool $parents=FALSE): Collection - { - return $this - ->getMustAttrs($parents) - ->merge($this->getMayAttrs($parents)); - } - /** * Adds an objectClass to the list of objectClasses that inherit * from this objectClass. @@ -93,16 +69,26 @@ final class ObjectClass extends Base */ public function getMayAttrs(bool $parents=FALSE): Collection { - $attrs = $this->may_attrs; + $attrs = $this->attributes + ->filter(fn($item)=>! $item->is_must) + ->transform(function($item) { + $item->source = $this->name; + return $item; + }); if ($parents) foreach ($this->getParents() as $object_class) - $attrs = $attrs->merge($object_class->getMayAttrs($parents)); + $attrs = $attrs->merge($object_class + ->getMayAttrs($parents) + ->transform(function($item) use ($object_class) { + $item->source = $item->source ?: $object_class->name; + return $item; + })); // Return a sorted list return $attrs ->unique(fn($item)=>$item->name) - ->sortBy(fn($item)=>strtolower($item->name.$item->source)); + ->sortBy(fn($item)=>$item->name); } /** @@ -119,16 +105,26 @@ final class ObjectClass extends Base */ public function getMustAttrs(bool $parents=FALSE): Collection { - $attrs = $this->must_attrs; + $attrs = $this->attributes + ->filter(fn($item)=>$item->is_must) + ->transform(function($item) { + $item->source = $this->name; + return $item; + }); if ($parents) foreach ($this->getParents() as $object_class) - $attrs = $attrs->merge($object_class->getMustAttrs($parents)); + $attrs = $attrs->merge($object_class + ->getMustAttrs($parents) + ->transform(function($item) use ($object_class) { + $item->source = $item->source ?: $object_class->name; + return $item; + })); // Return a sorted list return $attrs ->unique(fn($item)=>$item->name) - ->sortBy(fn($item)=>strtolower($item->name.$item->source)); + ->sortBy(fn($item)=>$item->name); } /** @@ -165,16 +161,6 @@ final class ObjectClass extends Base return $this->type === Server::OC_AUXILIARY; } - /** - * Determine if an array is listed in the may_force attrs - */ - public function isForceMay(string $attr): bool - { - return $this->may_force - ->pluck('name') - ->contains($attr); - } - public function isStructural(): bool { return $this->type === Server::OC_STRUCTURAL; @@ -192,9 +178,7 @@ final class ObjectClass extends Base Log::debug(sprintf('%s:Parsing ObjectClass [%s]',self::LOGKEY,$line)); // Init - $this->may_attrs = collect(); - $this->may_force = collect(); - $this->must_attrs = collect(); + $this->attributes = collect(); $this->sup_classes = collect(); $this->child_classes = collect(); @@ -204,34 +188,8 @@ final class ObjectClass extends Base protected function parse_chunk(array $strings,int &$i): void { switch ($strings[$i]) { - 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 'SUP': - if ($strings[$i+1] != '(') { + if ($strings[$i+1] !== '(') { $this->sup_classes->push(preg_replace("/'/",'',$strings[++$i])); } else { @@ -240,7 +198,7 @@ final class ObjectClass extends Base do { $i++; - if ($strings[$i] != '$') + if ($strings[$i] !== '$') $this->sup_classes->push(preg_replace("/'/",'',$strings[$i])); } while (! preg_match('/\)+\)?/',$strings[$i+1])); @@ -276,23 +234,17 @@ final class ObjectClass extends Base $i = $this->parseList(++$i,$strings,$attrs); - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('%s:= parseList returned %d (%s)',self::LOGKEY,$i,$attrs->join(','))); - foreach ($attrs as $string) { - $attr = new ObjectClassAttribute($string,$this->name); + $attr = clone config('server')->schema('attributetypes',$string); - if (config('server')->isForceMay($attr->getName())) { - $this->may_force->push($attr); - $this->may_attrs->push($attr); + if (! $attr->forced_as_may) + $attr->setMust(); - } else - $attr->required = TRUE; - $this->must_attrs->push($attr); + $this->attributes->push($attr); } if (static::DEBUG_VERBOSE) - Log::debug(sprintf('%s:- Case MUST returned (%s) (%s)',self::LOGKEY,$this->must_attrs->join(','),$this->may_force->join(','))); + Log::debug(sprintf('%s:- Case MUST returned (%s) (%s)',self::LOGKEY,$attrs->join(','),$this->forced_as_may ? 'FORCED MAY' : 'MUST')); break; case 'MAY': @@ -300,16 +252,11 @@ final class ObjectClass extends Base $i = $this->parseList(++$i,$strings,$attrs); - if (static::DEBUG_VERBOSE) - Log::debug(sprintf('%s:- parseList returned %d (%s)',self::LOGKEY,$i,$attrs->join(','))); - - foreach ($attrs as $string) { - $attr = new ObjectClassAttribute($string,$this->name); - $this->may_attrs->push($attr); - } + 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,$this->may_attrs->join(','))); + Log::debug(sprintf('%s:- Case MAY returned (%s)',self::LOGKEY,$attrs->join(','))); break; default: diff --git a/app/Classes/LDAP/Schema/ObjectClassAttribute.php b/app/Classes/LDAP/Schema/ObjectClassAttribute.php deleted file mode 100644 index 79cd475b..00000000 --- a/app/Classes/LDAP/Schema/ObjectClassAttribute.php +++ /dev/null @@ -1,40 +0,0 @@ -name = $name; - $this->source = $source; - } - - public function __get(string $key): mixed - { - return match ($key) { - 'source' => $this->source, - default => parent::__get($key), - }; - } -} \ No newline at end of file diff --git a/app/Classes/LDAP/Server.php b/app/Classes/LDAP/Server.php index 39f478be..cbbfa4eb 100644 --- a/app/Classes/LDAP/Server.php +++ b/app/Classes/LDAP/Server.php @@ -50,10 +50,6 @@ final class Server public function __get(string $key): mixed { return match($key) { - 'attributetypes' => $this->attributetypes, - 'ldapsyntaxes' => $this->ldapsyntaxes, - 'matchingrules' => $this->matchingrules, - 'objectclasses' => $this->objectclasses, 'config' => config(sprintf('ldap.connections.%s',config('ldap.default'))), 'name' => Arr::get($this->config,'name',__('No Server Name Yet')), default => throw new Exception('Unknown key:'.$key), @@ -298,15 +294,36 @@ final class Server } /** - * This function determines if the specified attribute is contained in the force_may list - * as configured in config.php. + * Get an attribute key for an attributetype name * - * @return boolean True if the specified attribute is configured to be force as a may attribute - * @todo There are 3 isForceMay() functions - we only need one + * @param string $key + * @return int|bool */ - public function isForceMay($attr_name): bool + public function get_attr_id(string $key): int|bool { - return in_array($attr_name,config('pla.force_may',[])); + static $attributes = $this->schema('attributetypes'); + + $attrid = $attributes->search(fn($item)=>$item->names->contains($key)); + + // Second chance search using lowercase items (our Entry attribute keys are lowercase) + if ($attrid === FALSE) + $attrid = $attributes->search(fn($item)=>$item->names_lc->contains(strtolower($key))); + + return $attrid; + } + + /** + * Given an OID, return the ldapsyntax for the OID + * + * @param string $oid + * @return LDAPSyntax|null + * @throws InvalidUsage + */ + public function get_syntax(string $oid): ?LDAPSyntax + { + return (($id=$this->schema('ldapsyntaxes')->search(fn($item)=>$item->oid === $oid)) !== FALSE) + ? $this->ldapsyntaxes[$id] + : NULL; } /** @@ -327,177 +344,157 @@ final class Server * @param string $item Schema Item to Fetch * @param string|null $key * @return Collection|LDAPSyntax|Base|NULL + * @throws InvalidUsage */ public function schema(string $item,?string $key=NULL): Collection|LDAPSyntax|Base|NULL { // Ensure our item to fetch is lower case $item = strtolower($item); - if ($key) - $key = strtolower($key); - $result = Cache::remember('schema'.$item,config('ldap.cache.time'),function() use ($item) { - // First pass if we have already retrieved the schema item - switch ($item) { - case 'attributetypes': - case 'ldapsyntaxes': - case 'matchingrules': - case 'objectclasses': - if ($this->{$item}->count()) - return $this->{$item}; + if (! $this->{$item}->count()) { + $this->{$item} = Cache::remember('schema.'.$item,config('ldap.cache.time'),function() use ($item) { + // Try to get the schema DN from the specified entry. + $schema_dn = $this->schemaDN(); + // @note: 389DS does not return subschemaSubentry unless it is requested + $schema = $this->fetch($schema_dn,['*','+','subschemaSubentry']); - break; + // If our schema's null, we didnt find it. + if (! $schema) + throw new Exception('Couldnt find schema at:'.$schema_dn); - // This error message is not localized as only developers should ever see it - default: - throw new InvalidUsage('Invalid request to fetch schema: '.$item); - } + switch ($item) { + case 'attributetypes': + Log::debug(sprintf('%s:Attribute Types',self::LOGKEY)); + // build the array of attribueTypes + //$syntaxes = $this->SchemaSyntaxes($dn); - // Try to get the schema DN from the specified entry. - $schema_dn = $this->schemaDN(); - // @note: 389DS does not return subschemaSubentry unless it is requested - $schema = $this->fetch($schema_dn,['*','+','subschemaSubentry']); + foreach ($schema->{$item} as $line) { + if (is_null($line) || ! strlen($line)) + continue; - // If our schema's null, we didnt find it. - if (! $schema) - throw new Exception('Couldnt find schema at:'.$schema_dn); + $o = new AttributeType($line); + $this->attributetypes->push($o); + } - switch ($item) { - case 'attributetypes': - Log::debug(sprintf('%s:Attribute Types',self::LOGKEY)); - // build the array of attribueTypes - //$syntaxes = $this->SchemaSyntaxes($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); - foreach ($schema->{$item} as $line) { - if (is_null($line) || ! strlen($line)) - continue; + if (! $this->attributetypes[$attrid]->children->contains($o->oid)) + $this->attributetypes[$attrid]->addChild($o->oid); + } - $o = new AttributeType($line); - $this->attributetypes->put($o->name_lc,$o); - } + // 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); - // 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); + /* 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); } } - } - // Now go through and reference the parent/child relationships - foreach ($this->attributetypes as $o) - if ($o->sup_attribute) { - $parent = strtolower($o->sup_attribute); + return $this->attributetypes; - if ($this->attributetypes->has($parent) !== FALSE) - $this->attributetypes[$parent]->addChild($o->name); + case 'ldapsyntaxes': + Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY)); + + foreach ($schema->{$item} as $line) { + if (is_null($line) || ! strlen($line)) + continue; + + $o = new LDAPSyntax($line); + $this->ldapsyntaxes->push($o); } - // 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); + return $this->ldapsyntaxes; - /* 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); - } - } + case 'matchingrules': + Log::debug(sprintf('%s:Matching Rules',self::LOGKEY)); - // Add the used in and required_by values. - foreach ($this->schema('objectclasses') as $object_class) { - // Add Used In. - foreach ($object_class->getAllAttrs()->pluck('name') as $attr_name) - if ($this->attributetypes->has(strtolower($attr_name))) - $this->attributetypes[strtolower($attr_name)]->addUsedInObjectClass($object_class->name,$object_class->isStructural()); + foreach ($schema->{$item} as $line) { + if (is_null($line) || ! strlen($line)) + continue; - // Add Required By. - foreach ($object_class->getMustAttrs()->pluck('name') 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->may_force 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)); - - foreach ($schema->{$item} as $line) { - if (is_null($line) || ! strlen($line)) - continue; - - $o = new MatchingRule($line); - $this->matchingrules->put($o->name_lc,$o); - } - - foreach ($this->schema('attributetypes') as $attr) { - $rule_key = strtolower($attr->getEquality()); - - 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->sup_classes as $parent) { - $parent = strtolower($parent); - - if (! $this->objectclasses->contains($parent)) - $this->objectclasses[$parent]->addChildObjectClass($o->name); + $o = new MatchingRule($line); + $this->matchingrules->push($o); } - return $this->objectclasses; + foreach ($this->schema('attributetypes') as $attr) { + $rule_id = $this->matchingrules->search(fn($item)=>$item->oid === $attr->equality); - // Shouldnt get here - default: - throw new InvalidUsage('Invalid request to fetch schema: '.$item); - } - }); + if ($rule_id !== FALSE) + $this->matchingrules[$rule_id]->addUsedByAttr($attr->name); + } - return is_null($key) ? $result : $result->get($key); + 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->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)); + } } /** @@ -510,15 +507,4 @@ final class Server { return Arr::get($this->rootDSE->subschemasubentry,0); } - - /** - * Given an OID, return the ldapsyntax for the OID - * - * @param string $oid - * @return LDAPSyntax|null - */ - public function schemaSyntaxName(string $oid): ?LDAPSyntax - { - return $this->schema('ldapsyntaxes',$oid); - } } \ No newline at end of file diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 391e0079..3507851e 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -44,19 +44,21 @@ class HomeController extends Controller $o = new Entry; $o->setRDNBase($key['dn']); + foreach (collect(old())->except(['_token','key','step','rdn','rdn_value','userpassword_hash']) as $old => $value) + $o->{$old} = array_filter($value); + if (count($x=collect(old('objectclass',$request->validated('objectclass')))->dot()->filter())) { $o->objectclass = Arr::undot($x); // Also add in our required attributes - foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao) + foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->is_must) as $ao) $o->{$ao->name} = [Entry::TAG_NOTAG=>'']; } elseif ($request->validated('template')) { $template = $o->template($request->validated('template')); $o->objectclass = [Entry::TAG_NOTAG=>$template->objectclasses->toArray()]; - // @todo We need to add aliases - foreach($o->getAvailableAttributes()->filter(fn($item)=>$template->attributes->contains($item)) as $ao) + foreach ($o->getAvailableAttributes()->filter(fn($item)=>$item->names_lc->intersect($template->attributes)->count()) as $ao) $o->{$ao->name} = [Entry::TAG_NOTAG=>'']; } diff --git a/app/Http/Requests/EntryAddRequest.php b/app/Http/Requests/EntryAddRequest.php index dacbd2c2..433fa048 100644 --- a/app/Http/Requests/EntryAddRequest.php +++ b/app/Http/Requests/EntryAddRequest.php @@ -35,9 +35,18 @@ class EntryAddRequest extends FormRequest return []; $r = request() ?: collect(); + $rk = array_keys($r->all()); + return config('server') ->schema('attributetypes') - ->intersectByKeys($r->all()) + ->filter(fn($item)=>$item->names_lc->intersect($rk)->count()) + ->transform(function($item) use ($rk) { + // Set the attributetype name + if (($x=$item->names_lc->intersect($rk))->count() === 1) + $item->setName($x->pop()); + + return $item; + }) ->map(fn($item)=>$item->validation($r->get('objectclass',[]))) ->filter() ->flatMap(fn($item)=>$item) diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index bf86f830..9dca8c15 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -202,7 +202,7 @@ class Entry extends Model // If the attribute name has tags list($attribute,$tag) = $this->keytag($key); - if (! config('server')->schema('attributetypes')->has($attribute)) + if (config('server')->get_attr_id($attribute) === FALSE) throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute)); $o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]); @@ -312,7 +312,7 @@ class Entry extends Model $result = collect(); foreach (($this->getObject('objectclass')?->values ?: []) as $oc) - $result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes); + $result = $result->merge(config('server')->schema('objectclasses',$oc)->all_attributes); return $result; } @@ -415,9 +415,6 @@ class Entry extends Model * Return a list of attributes without any values * * @return Collection - * @todo Dont show attributes that are not provided by an objectclass, make a new function to show those - * This is for dynamic list items eg: labeledURI, which are not editable. - * We can highlight those values that are as a result of a dynamic module */ public function getMissingAttributes(): Collection { @@ -430,7 +427,7 @@ class Entry extends Model $o = new Attribute\RDN('','dn',['']); // @todo for an existing object, rdnbase would be null, so dynamically get it from the DN. $o->setBase($this->rdnbase); - $o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required)); + $o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->is_must)); return $o; } diff --git a/resources/views/components/attribute/password.blade.php b/resources/views/components/attribute/password.blade.php index d8fb87bc..01b9f2da 100644 --- a/resources/views/components/attribute/password.blade.php +++ b/resources/views/components/attribute/password.blade.php @@ -4,7 +4,7 @@ @foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value) @if($edit)
- + ($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)>
diff --git a/resources/views/components/attribute/rdn.blade.php b/resources/views/components/attribute/rdn.blade.php index fc7c23b9..795d5ad1 100644 --- a/resources/views/components/attribute/rdn.blade.php +++ b/resources/views/components/attribute/rdn.blade.php @@ -8,7 +8,7 @@ @foreach($o->attrs->map(fn($item)=>['id'=>$item,'value'=>$item]) as $option) @continue(! Arr::get($option,'value')) - + @endforeach diff --git a/resources/views/fragment/schema/attributetypes.blade.php b/resources/views/fragment/schema/attributetypes.blade.php index 0083f80d..891b8162 100644 --- a/resources/views/fragment/schema/attributetypes.blade.php +++ b/resources/views/fragment/schema/attributetypes.blade.php @@ -2,19 +2,19 @@
- @foreach($attributetypes as $o) - + @foreach($at as $o) + - + @@ -57,7 +57,7 @@ - + @@ -77,11 +77,8 @@
{{ $o->name }}{{ $o->names->join(' / ') }}
@lang('Substring Rule'){{ $o->sub_str_rule ?: __('(not specified)') }}
@lang('Syntax'){{ ($o->syntax_oid && $x=$server->schemaSyntaxName($o->syntax_oid)) ? $x->description : __('(unknown syntax)') }} @if($o->syntax_oid)({{ $o->syntax_oid }})@endif@lang('Syntax'){{ ($o->syntax_oid && $x=$server->get_syntax($o->syntax_oid)) ? $x->description : __('(unknown syntax)') }} @if($o->syntax_oid)({{ $o->syntax_oid }})@endif
@lang('Single Valued')@lang($o->is_single_value ? 'Yes' : 'No')
@lang('Aliases') - @if($o->aliases->count()) - @foreach($o->aliases as $alias) - @if($loop->index) @endif - {{ $alias }} - @endforeach + @if($o->names->count() > 1) + {!! $o->names->join(', ') !!} @else @lang('(none)') @endif diff --git a/resources/views/frames/create.blade.php b/resources/views/frames/create.blade.php index 3125c827..8326a638 100644 --- a/resources/views/frames/create.blade.php +++ b/resources/views/frames/create.blade.php @@ -70,6 +70,8 @@ @endforeach @if(! $template) + + @include('fragment.dn.add_attr') @endif @break;