diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index f1002662..1fcc33ee 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -20,9 +20,6 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator // Is this attribute an internal attribute protected(set) bool $is_internal = FALSE; - // Is this attribute the RDN? - public bool $is_rdn = FALSE; - // MIN/MAX number of values protected(set) int $min_values_count = 0; protected(set) int $max_values_count = 0; @@ -40,7 +37,7 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator protected(set) Collection $values_old; // Current Values public Collection $values; - // The other object classes of the entry that include this attribute + // The objectclasses of the entry that has this attribute protected(set) Collection $oc; /* @@ -100,7 +97,7 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator * @param string $dn DN this attribute is used in * @param string $name Name of the attribute * @param array $values Current Values - * @param array $oc ObjectClasses that the DN has, that includes this attribute + * @param array $oc The objectclasses that the DN of this attribute has */ public function __construct(string $dn,string $name,array $values,array $oc=[]) { @@ -143,6 +140,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator 'hints' => $this->hints(), // Can this attribute be edited 'is_editable' => $this->schema ? $this->schema->{$key} : NULL, + // Objectclasses that required this attribute for an LDAP entry + 'required' => $this->required(), + // 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}, // Attribute name in lower case @@ -232,11 +233,8 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator // If this attribute name is an alias for the schema attribute name // @todo - // objectClasses requiring this attribute - // @todo limit this to this DNs objectclasses - // 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(','))); + if ($this->required()->count()) + $result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', '))); // This attribute has language tags if ($this->lang_tags->count()) @@ -256,6 +254,22 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator || ($this->values->diff($this->values_old)->count() !== 0); } + /** + * Work out if this attribute is an RDN attribute + * + * @return bool + */ + public function isRDN(): bool + { + // If we dont have an DN, then we cant know + if (! $this->dn) + return FALSE; + + $rdns = collect(explode('+',substr($this->dn,0,strpos($this->dn,',')))); + + return $rdns->filter(fn($item) => str_starts_with($item,$this->name.'='))->count() > 0; + } + /** * Display the attribute value * @@ -287,6 +301,19 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator return Arr::get($this->values,$key); } + /** + * Work out if this attribute is required by an objectClass the entry has + * + * @return Collection + */ + public function required(): Collection + { + // If we dont have any objectclasses then we cant know if it is required + return $this->oc->count() + ? $this->oc->intersect($this->schema->required_by_object_classes->keys())->sort() + : collect(); + } + /** * If this attribute has RFC3866 Language Tags, this will enable those values to be captured * diff --git a/app/Classes/LDAP/Attribute/ObjectClass.php b/app/Classes/LDAP/Attribute/ObjectClass.php index 2667b0be..959429b3 100644 --- a/app/Classes/LDAP/Attribute/ObjectClass.php +++ b/app/Classes/LDAP/Attribute/ObjectClass.php @@ -21,15 +21,15 @@ final class ObjectClass extends Attribute * @param string $dn DN this attribute is used in * @param string $name Name of the attribute * @param array $values Current Values - * @param array $oc ObjectClasses that the DN has, that includes this attribute + * @param array $oc The objectclasses that the DN of this attribute has */ public function __construct(string $dn,string $name,array $values,array $oc=[]) { - parent::__construct($dn,$name,$values,$oc); + parent::__construct($dn,$name,$values,['top']); $this->oc_schema = config('server') ->schema('objectclasses') - ->filter(fn($item)=>$this->values->contains($item->name)); + ->filter(fn($item)=>$this->values->merge($this->values_old)->unique()->contains($item->name)); } public function __get(string $key): mixed diff --git a/app/Classes/LDAP/Schema/AttributeType.php b/app/Classes/LDAP/Schema/AttributeType.php index 49d8f841..a33631d2 100644 --- a/app/Classes/LDAP/Schema/AttributeType.php +++ b/app/Classes/LDAP/Schema/AttributeType.php @@ -320,11 +320,12 @@ final class AttributeType extends Base { * that is the list of objectClasses which must have this attribute. * * @param string $name The name of the objectClass to add. + * @param bool $structural */ - public function addRequiredByObjectClass(string $name): void + public function addRequiredByObjectClass(string $name,bool $structural): void { - if (! $this->required_by_object_classes->contains($name)) - $this->required_by_object_classes->push($name); + if (! $this->required_by_object_classes->has($name)) + $this->required_by_object_classes->put($name,$structural); } /** @@ -332,6 +333,7 @@ final class AttributeType extends Base { * that is the list of objectClasses which provide this attribute. * * @param string $name The name of the objectClass to add. + * @param bool $structural */ public function addUsedInObjectClass(string $name,bool $structural): void { @@ -544,7 +546,7 @@ final class AttributeType extends Base { */ public function validation(array $array): ?array { - // For each item in array, we need to get the OC heirachy + // For each item in array, we need to get the OC hierarchy $heirachy = collect($array) ->filter() ->map(fn($item)=>config('server') diff --git a/app/Classes/LDAP/Server.php b/app/Classes/LDAP/Server.php index cf2e3695..19bd9346 100644 --- a/app/Classes/LDAP/Server.php +++ b/app/Classes/LDAP/Server.php @@ -428,7 +428,7 @@ final class Server // 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); + $this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name,$object_class->isStructural()); // Force May foreach ($object_class->getForceMayAttrs() as $attr_name) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index c11cc4b5..c47bce69 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -92,10 +92,10 @@ class HomeController extends Controller return $request->noheader ? view(sprintf('components.attribute.widget.%s',$id)) - ->with('o',Factory::create($dn,$id,[],$request->oc ?: [])) + ->with('o',Factory::create($dn,$id,[],$request->objectclasses)) ->with('value',$request->value) ->with('loop',$xx) - : (new AttributeType(Factory::create($dn,$id,[],$request->oc ?: []),TRUE,collect($request->oc ?: [])))->render(); + : new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),TRUE)->render(); } public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 937efcd5..0fc56313 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -16,7 +16,9 @@ use App\Exceptions\InvalidUsage; class Entry extends Model { + // Our Attribute objects private Collection $objects; + /* @deprecated */ private bool $noObjectAttributes = FALSE; // For new entries, this is the container that this entry will be stored in private string $rdnbase; @@ -176,6 +178,7 @@ class Entry extends Model private function getAttributesAsObjects(): Collection { $result = collect(); + $entry_oc = Arr::get($this->attributes,'objectclass',[]); foreach ($this->attributes as $attribute => $values) { // If the attribute name has tags @@ -184,17 +187,22 @@ class Entry extends Model $attribute = $matches[1]; // If the attribute doesnt exist we'll create it - $o = Arr::get($result,$attribute,Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),Arr::get($this->original,'objectclass',[]))); + $o = Arr::get( + $result, + $attribute, + Factory::create( + $this->dn, + $attribute, + Arr::get($this->original,$attribute,[]), + $entry_oc, + )); $o->setLangTag($matches[3],$values); } else { - $o = Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),Arr::get($this->original,'objectclass',[])); + $o = Factory::create($this->dn,$attribute,Arr::get($this->original,$attribute,[]),$entry_oc); } if (! $result->has($attribute)) { - // Set the rdn flag - $o->is_rdn = preg_match('/^'.$attribute.'=/i',$this->dn); - // Store our new values to know if this attribute has changed $o->values = collect($values); @@ -306,7 +314,7 @@ class Entry extends Model private function getRDNObject(): Attribute\RDN { $o = new Attribute\RDN('','dn',['']); - // @todo for an existing object, return the base. + // @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)); @@ -430,6 +438,7 @@ class Entry extends Model * Dont convert our $this->attributes to $this->objects when creating a new Entry::class * * @return $this + * @deprecated */ public function noObjectAttributes(): static { diff --git a/app/View/Components/AttributeType.php b/app/View/Components/AttributeType.php index 36477a73..6c3dcb57 100644 --- a/app/View/Components/AttributeType.php +++ b/app/View/Components/AttributeType.php @@ -11,17 +11,15 @@ use App\Classes\LDAP\Attribute as LDAPAttribute; class AttributeType extends Component { - public Collection $oc; - public LDAPAttribute $o; - public bool $new; + private LDAPAttribute $o; + private bool $new; /** * Create a new component instance. */ - public function __construct(LDAPAttribute $o,bool $new=FALSE,?Collection $oc=NULL) + public function __construct(LDAPAttribute $o,bool $new=FALSE) { $this->o = $o; - $this->oc = $oc; $this->new = $new; } @@ -32,7 +30,6 @@ class AttributeType extends Component { return view('components.attribute-type') ->with('o',$this->o) - ->with('oc',$this->oc) ->with('new',$this->new); } } \ No newline at end of file diff --git a/resources/views/fragment/schema/attributetypes.blade.php b/resources/views/fragment/schema/attributetypes.blade.php index ba25b200..e5c3d7be 100644 --- a/resources/views/fragment/schema/attributetypes.blade.php +++ b/resources/views/fragment/schema/attributetypes.blade.php @@ -106,6 +106,24 @@ @endif </td> </tr> + <tr> + <td>@lang('Required by ObjectClasses')</td> + <td> + @if ($o->required_by_object_classes->count()) + @foreach ($o->required_by_object_classes as $class => $structural) + @if($structural) + <strong> + @endif + <a class="objectclass" id="{{ strtolower($class) }}" href="#{{ strtolower($class) }}">{{ $class }}</a> + @if($structural) + </strong> + @endif + @endforeach + @else + @lang('(none)') + @endif + </td> + </tr> <tr> <td>@lang('Force as MAY by config')</td><td><strong>@lang($o->forced_as_may ? 'Yes' : 'No')</strong></td> </tr>