From c0c9a5576ea6b6c2888b39e9aac9280685c0f181 Mon Sep 17 00:00:00 2001 From: Deon George Date: Thu, 2 Mar 2023 14:41:38 +1100 Subject: [PATCH] Added rendering attribute hints --- app/Classes/LDAP/Attribute.php | 147 +++++++++++------- .../LDAP/Attribute/Binary/JpegPhoto.php | 2 +- app/Classes/LDAP/Attribute/Internal.php | 2 +- app/Classes/LDAP/Schema/AttributeType.php | 12 ++ app/Ldap/Entry.php | 13 +- lib/PageRender.php | 43 ----- lib/functions.php | 28 ---- resources/views/frames/dn.blade.php | 17 +- 8 files changed, 125 insertions(+), 139 deletions(-) diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index a63ebf1..a2d9263 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -4,6 +4,8 @@ namespace App\Classes\LDAP; use Illuminate\Support\Collection; +use App\Classes\LDAP\Schema\AttributeType; + /** * Represents an attribute of an LDAP Object */ @@ -12,6 +14,8 @@ class Attribute // Attribute Name protected string $name; + protected ?AttributeType $schema; + /* # Source of this attribute definition protected $source; @@ -21,10 +25,15 @@ class Attribute protected Collection $values; // Can this attribute be deleted - protected bool $deletable = FALSE; + protected bool $is_deletable = FALSE; // Is this attribute an internal attribute - protected bool $internal; + protected bool $is_internal; + + // Is this attribute the RDN? + protected bool $is_rdn = FALSE; + + protected Collection $required_by; /* protected $oldvalues = array(); @@ -74,7 +83,6 @@ class Attribute public $page = 1; public $order = 255; public $ordersort = 255; - public $rdn = false; # Schema Aliases for this attribute (stored in lowercase) protected $aliases = array(); @@ -89,6 +97,8 @@ class Attribute $this->name = $name; $this->values = collect($values); + $this->schema = config('server')->schema('attributetypes',$name); + /* # Should this attribute be hidden if ($server->isAttrHidden($this->name)) @@ -106,16 +116,24 @@ class Attribute public function __get(string $key): mixed { - switch ($key) { - case 'name': - return $this->{$key}; + return match ($key) { + // Can this attribute have more values + 'can_addvalues' => FALSE, // @todo + // Schema attribute description + 'description' => $this->schema ? $this->schema->{$key} : NULL, + // Attribute hints + 'hints' => $this->hints(), + // Is this an internal attribute + 'is_internal' => isset($this->{$key}) && $this->{$key}, + // We prefer the name as per the schema if it exists + 'name' => $this->schema ? $this->schema->{$key} : $this->{$key}, + // Attribute name in lower case + 'name_lc' => strtolower($this->name), + // Is this attribute the RDN + 'rdn' => $this->is_rdn, - case 'internal': return isset($this->{$key}) && $this->{$key}; - case 'name_lc': return strtolower($this->name); - - default: - throw new \Exception('Unknown key:'.$key); - } + default => throw new \Exception('Unknown key:' . $key), + }; } /** @@ -128,6 +146,64 @@ class Attribute return $this->values->join('
'); } + /** + * Return an instance of this attribute that is deletable. + * This is primarily used for rendering to know if to render delete options. + * + * @return Attribute + */ + public function deletable(): self + { + $clone = clone $this; + + if (! $this->required_by->count()) + $clone->is_deletable = TRUE; + + return $clone; + } + + /** + * Return the hints about this attribute, ie: RDN, Required, etc + * + * @return array + */ + public function hints(): array + { + $result = collect(); + + // Is this Attribute an RDN + 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 + + // objectClasses requiring this attribute + // eg: $result->put('required','Required by objectClasses: a,b'); + if ($this->required_by->count()) + $result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required_by->join(','))); + + return $result->toArray(); + } + + /** + * Set the objectclasses that require this attribute + * + * @param Collection $oc + * @return Collection + */ + public function required_by(Collection $oc): Collection + { + return $this->required_by = ($this->schema + ? $oc->intersect($this->schema->required_by_object_classes) + : collect()); + } + + public function setRDN(): void + { + $this->is_rdn = TRUE; + } + /** * Return the name of the attribute. * @@ -354,20 +430,6 @@ class Attribute $this->justModified(); } - public function isInternal() { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->internal); - - return $this->internal; - } - - public function setInternal() { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs); - - $this->internal = true; - } - public function isRequired() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs); @@ -644,25 +706,6 @@ class Attribute return $this->verify; } - public function setRDN($rdn) { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs); - - $this->rdn = $rdn; - } - - /** - * Return if this attribute is an RDN attribute - * - * @return boolean - * - public function isRDN() { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs,$this->rdn); - - return $this->rdn; - } - /** * Capture all the LDAP details we are interested in * @@ -921,18 +964,4 @@ class Attribute debug_dump_backtrace(sprintf('Unknown JS request %s',$type),1); } */ - - /** - * Return an instance of this attribute that is deletable. - * This is primarily used for rendering to know if to render delete options. - * - * @return Attribute - */ - public function deletable(): self - { - $clone = clone $this; - $clone->deletable = TRUE; - - return $clone; - } } \ No newline at end of file diff --git a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php index 4c56af4..ac44259 100644 --- a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php +++ b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php @@ -30,7 +30,7 @@ final class JpegPhoto extends Binary $result .= sprintf('%s', $x, base64_encode($value), - $this->deletable ? sprintf('
%s',__('Delete')) : ''); + $this->is_deletable ? sprintf('
%s',__('Delete')) : ''); } } diff --git a/app/Classes/LDAP/Attribute/Internal.php b/app/Classes/LDAP/Attribute/Internal.php index 5f94089..46279d1 100644 --- a/app/Classes/LDAP/Attribute/Internal.php +++ b/app/Classes/LDAP/Attribute/Internal.php @@ -9,5 +9,5 @@ use App\Classes\LDAP\Attribute; */ abstract class Internal extends Attribute { - protected bool $internal = TRUE; + protected bool $is_internal = TRUE; } \ No newline at end of file diff --git a/app/Classes/LDAP/Schema/AttributeType.php b/app/Classes/LDAP/Schema/AttributeType.php index cec4b08..85d3129 100644 --- a/app/Classes/LDAP/Schema/AttributeType.php +++ b/app/Classes/LDAP/Schema/AttributeType.php @@ -262,6 +262,7 @@ class AttributeType extends Base { 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; @@ -528,6 +529,17 @@ class AttributeType extends Base { $this->aliases->forget($x); } + /** + * Given a list of object classes, determine if this is a required attribute + * + * @param Collection $oc List of objectclasses to compare. + * @return Collection + */ + public function required_by(Collection $oc): Collection + { + return $oc->diff($this->required_by_object_classes); + } + /** * Sets this attribute's list of aliases. * diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index af66537..b6fe4bc 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -17,7 +17,16 @@ class Entry extends Model $result = collect(); foreach (parent::getAttributes() as $attribute => $value) { - $result->put($attribute,Factory::create($attribute,$value)); + $o = Factory::create($attribute,$value); + + // Set the rdn flag + if (preg_match('/^'.$attribute.'=/i',$this->dn)) + $o->setRDN(); + + // Set required flag + $o->required_by(collect($this->getAttribute('objectclass'))); + + $result->put($attribute,$o); } $sort = collect(config('ldap.attr_display_order',[]))->transform(function($item) { return strtolower($item); }); @@ -67,7 +76,7 @@ class Entry extends Model public function getVisibleAttributes(): Collection { return collect($this->getAttributes())->filter(function($item) { - return ! $item->internal; + return ! $item->is_internal; }); } diff --git a/lib/PageRender.php b/lib/PageRender.php index d905c96..5d88498 100644 --- a/lib/PageRender.php +++ b/lib/PageRender.php @@ -430,49 +430,6 @@ class PageRender extends Visitor { return ''; } - #@todo this function shouldnt re-calculate requiredness, it should be known in the template already - need to set the ldaptype when initiating the attribute. - protected function getNoteRequiredAttribute($attribute) { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',129,0,__FILE__,__LINE__,__METHOD__,$fargs); - - if (DEBUGTMP) printf('%s
',__METHOD__); - - $required_by = ''; - $sattr_required = ''; - - # Is this attribute required by an objectClass ? - $sattr = $this->getServer()->getSchemaAttribute($attribute->getName()); - if ($sattr) - $sattr_required = $sattr->getRequiredByObjectClasses(); - - if ($sattr_required) { - $oc = $this->template->getAttribute('objectclass'); - - if ($oc) - foreach ($oc->getValues() as $objectclass) { - # If this objectclass is in our required list - if (in_array_ignore_case($objectclass,$sattr_required)) { - $required_by .= sprintf('%s ',$objectclass); - continue; - } - - # If not, see if it is in our parent. - $sattr = $this->getServer()->getSchemaObjectClass($objectclass); - - if (array_intersect($sattr->getParents(),$sattr_required)) - $required_by .= sprintf('%s ',$objectclass); - } - - else - debug_dump_backtrace('How can there be no objectclasses?',1); - } - - if ($required_by) - return sprintf('%s',_('Required attribute for objectClass(es)'),$required_by,_('required')); - else - return ''; - } - protected function getNoteRDNAttribute($attribute) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) debug_log('Entered (%%)',129,0,__FILE__,__LINE__,__METHOD__,$fargs); diff --git a/lib/functions.php b/lib/functions.php index 2532b7c..c59902b 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -1766,34 +1766,6 @@ function random_salt($length) { return $str; } -/** - * Given a DN string, this returns the 'RDN' portion of the string. - * For example. given 'cn=Manager,dc=example,dc=com', this function returns - * 'cn=Manager' (it is really the exact opposite of ds_ldap::getContainer()). - * - * @param string The DN whose RDN to return. - * @param boolean If true, include attributes in the RDN string. See http://php.net/ldap_explode_dn for details - * @return string The RDN - */ -function get_rdn($dn,$include_attrs=0,$decode=false) { - if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) - debug_log('Entered (%%)',1,0,__FILE__,__LINE__,__METHOD__,$fargs); - - if (is_null($dn)) - return null; - - $rdn = pla_explode_dn($dn,$include_attrs); - if (! count($rdn) || ! isset($rdn[0])) - return $dn; - - if ($decode) - $rdn = dn_unescape($rdn[0]); - else - $rdn = $rdn[0]; - - return $rdn; -} - /** * Split an RDN into its attributes */ diff --git a/resources/views/frames/dn.blade.php b/resources/views/frames/dn.blade.php index dba7e57..e67267d 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -28,17 +28,24 @@ @foreach ($o->getVisibleAttributes() as $ao) - {{--
- {{ $ao->name }} + + {{ $ao->name }} - + + @foreach($ao->hints as $name => $description) + @if ($loop->index),@endif + {{ $name }} + @endforeach +
{!! $ao->deletable() !!}
- - {{ __('Add Value') }} + @if ($ao->can_addvalues) + + {{ __('Add Value') }} + @endif
@dump($ao)