diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index afa04bf..b4857d7 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -10,7 +10,7 @@ use App\Classes\LDAP\Schema\AttributeType; /** * Represents an attribute of an LDAP Object */ -class Attribute +class Attribute implements \Countable, \ArrayAccess { // Attribute Name protected string $name; @@ -41,6 +41,9 @@ class Attribute // RFC3866 Language Tags protected Collection $lang_tags; + // The old values for this attribute - helps with isDirty() to determine if there is an update pending + protected Collection $oldValues; + /* # Has the attribute been modified protected $modified = false; @@ -98,6 +101,7 @@ class Attribute $this->values = collect($values); $this->lang_tags = collect(); $this->required_by = collect(); + $this->oldValues = collect(); // No need to load our schema for internal attributes if (! $this->is_internal) @@ -151,6 +155,31 @@ class Attribute return $this->__get('name'); } + public function count(): int + { + return $this->values->count(); + } + + public function offsetExists(mixed $offset): bool + { + return ! is_null($this->values->get($offset)); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->values->get($offset); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + // We cannot set new values using array syntax + } + + public function offsetUnset(mixed $offset): void + { + // We cannot clear values using array syntax + } + /** * Return the hints about this attribute, ie: RDN, Required, etc * @@ -179,6 +208,24 @@ class Attribute return $result->toArray(); } + /** + * Determine if this attribute has changes + * + * @return bool + */ + public function isDirty(): bool + { + if ($this->oldValues->count() !== $this->values->count()) + return TRUE; + + return $this->values->diff($this->oldValues)->count() !== 0; + } + + public function oldValues(array $array): void + { + $this->oldValues = collect($array); + } + /** * Display the attribute value * @@ -190,6 +237,7 @@ class Attribute { return view('components.attribute') ->with('edit',$edit) + ->with('new',FALSE) ->with('o',$this); } diff --git a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php index 13910f2..a9ae277 100644 --- a/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php +++ b/app/Classes/LDAP/Attribute/Binary/JpegPhoto.php @@ -5,12 +5,15 @@ namespace App\Classes\LDAP\Attribute\Binary; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute\Binary; +use App\Traits\MD5Updates; /** * Represents an JpegPhoto Attribute */ final class JpegPhoto extends Binary { + use MD5Updates; + public function __construct(string $name,array $values) { parent::__construct($name,$values); diff --git a/app/Classes/LDAP/Attribute/Password.php b/app/Classes/LDAP/Attribute/Password.php index d1842b1..1a64c9c 100644 --- a/app/Classes/LDAP/Attribute/Password.php +++ b/app/Classes/LDAP/Attribute/Password.php @@ -5,12 +5,15 @@ namespace App\Classes\LDAP\Attribute; use Illuminate\Contracts\View\View; use App\Classes\LDAP\Attribute; +use App\Traits\MD5Updates; /** * Represents an attribute whose values are passwords */ final class Password extends Attribute { + use MD5Updates; + public function render(bool $edit=FALSE,bool $blank=FALSE): View { return view('components.attribute.password') diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 5f55bb3..b974232 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -15,6 +15,100 @@ class Entry extends Model /* OVERRIDES */ public function getAttributes(): array + { + return $this->getAttributesAsObjects()->toArray(); + } + + /** + * Determine if the new and old values for a given key are equivalent. + */ + protected function originalIsEquivalent(string $key): bool + { + if (! array_key_exists($key, $this->original)) { + return false; + } + + $current = $this->attributes[$key]; + $original = $this->original[$key]; + + if ($current === $original) { + return true; + } + + //dump(['key'=>$key,'current'=>$current,'original'=>$this->original[$key],'objectvalue'=>$this->getAttributeAsObject($key)->isDirty()]); + return ! $this->getAttributeAsObject($key)->isDirty(); + } + + public function getOriginal(): array + { + static $result = NULL; + + if (is_null($result)) { + $result = collect(); + + // @todo Optimise this foreach with getAttributes() + foreach (parent::getOriginal() as $attribute => $value) { + // If the attribute name has language tags + $matches = []; + if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) { + $attribute = $matches[1]; + + // If the attribute doesnt exist we'll create it + $o = Arr::get($result,$attribute,Factory::create($attribute,[])); + $o->setLangTag($matches[3],$value); + + } else { + $o = Factory::create($attribute,$value); + } + + if (! $result->has($attribute)) { + // 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); + } + } + } + + return $result->toArray(); + } + + /* ATTRIBUTES */ + + /** + * Return a key to use for sorting + * + * @todo This should be the DN in reverse order + * @return string + */ + public function getSortKeyAttribute(): string + { + return $this->getDn(); + } + + /* METHODS */ + + /** + * Get an attribute as an object + * + * @param string $key + * @return Attribute|null + */ + public function getAttributeAsObject(string $key): Attribute|null + { + return Arr::get($this->getAttributesAsObjects(),$key); + } + + /** + * Convert all our attribute values into an array of Objects + * + * @return Collection + */ + protected function getAttributesAsObjects(): Collection { static $result = NULL; @@ -43,6 +137,9 @@ class Entry extends Model // Set required flag $o->required_by(collect($this->getAttribute('objectclass'))); + // Store our original value to know if this attribute has changed + $o->oldValues(Arr::get($this->original,$attribute)); + $result->put($attribute,$o); } } @@ -73,36 +170,12 @@ class Entry extends Model // Case where at least one attribute or its friendly name is in $attrs_display_order // return -1 if $a before $b in $attrs_display_order return ($a_key < $b_key) ? -1 : 1; - } ])->toArray(); + } ]); } return $result; } - /* ATTRIBUTES */ - - /** - * Return a key to use for sorting - * - * @todo This should be the DN in reverse order - * @return string - */ - public function getSortKeyAttribute(): string - { - return $this->getDn(); - } - - /* METHODS */ - - /** - * Return a secure version of the DN - * @return string - */ - public function getDNSecure(): string - { - return Crypt::encryptString($this->getDn()); - } - /** * Return a list of available attributes - as per the objectClass entry of the record * @@ -118,6 +191,15 @@ class Entry extends Model return $result; } + /** + * Return a secure version of the DN + * @return string + */ + public function getDNSecure(): string + { + return Crypt::encryptString($this->getDn()); + } + /** * Return a list of LDAP internal attributes * diff --git a/app/Traits/MD5Updates.php b/app/Traits/MD5Updates.php new file mode 100644 index 0000000..85c442d --- /dev/null +++ b/app/Traits/MD5Updates.php @@ -0,0 +1,23 @@ +values->diff($this->oldValues) as $key => $value) + if (md5(Arr::get($this->oldValues,$key)) !== $value) + return TRUE; + + return FALSE; + } +} \ No newline at end of file diff --git a/resources/views/components/attribute/binary/jpegphoto.blade.php b/resources/views/components/attribute/binary/jpegphoto.blade.php index 13e8421..494e9c2 100644 --- a/resources/views/components/attribute/binary/jpegphoto.blade.php +++ b/resources/views/components/attribute/binary/jpegphoto.blade.php @@ -1,33 +1,40 @@ -@if($edit) -
-@endif + +
+
+
+
+ + @foreach ($o->values as $value) +
+
+ @switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE)) + @case('image/jpeg') + @default + + @endswitch + + + @endforeach +
+ + get($o->name_lc.'.'.$loop->index))is-invalid @endif /> - - - @foreach ($o->values as $value) - @switch ($x=$f->buffer($value,FILEINFO_MIME_TYPE)) - @case('image/jpeg') - @default - - @endswitch - @endforeach - -
- - + @if ($edit) +
+ + @lang('Delete') - @if($edit) -
@lang('Delete') - @endif -
+
+ @if($e) + {{ join('|',$e) }} + @endif +
+ @endif +
+
-@if($edit) -
- @if($e) - {{ join('|',$e) }} - @endif + + @include('components.attribute.widget.options')
- -
-@endif \ No newline at end of file +
\ No newline at end of file diff --git a/resources/views/components/attribute/password.blade.php b/resources/views/components/attribute/password.blade.php index 0c2c38c..8752e7f 100644 --- a/resources/views/components/attribute/password.blade.php +++ b/resources/views/components/attribute/password.blade.php @@ -1,11 +1,14 @@ -
-
+ +
+
+
- @foreach (old($o->name_lc,$o->values) as $value) + @foreach ($o->values as $value) @if ($edit)
- + +
@if($e) {{ join('|',$e) }} @@ -17,9 +20,11 @@ @endif @endforeach
-
-
- @lang('Check Password') + @include('components.attribute.widget.options') + + + @lang('Check Password') +
-
\ No newline at end of file +
diff --git a/resources/views/components/attribute/widget/options.blade.php b/resources/views/components/attribute/widget/options.blade.php index a27e8ff..5739bda 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -1,7 +1,7 @@ @if($o->is_rdn) @lang('Rename') @elseif($edit && $o->can_addvalues) -
+ @lang('Add Value') @if($new) @endif -
+ @endif @section('page-scripts') diff --git a/resources/views/frames/update.blade.php b/resources/views/frames/update.blade.php index 951aab9..3edfb36 100644 --- a/resources/views/frames/update.blade.php +++ b/resources/views/frames/update.blade.php @@ -3,7 +3,7 @@ @section('page_title') - + @@ -89,7 +89,7 @@ @endif - + @endfor
{!! $x ? $x->render() : sprintf('
',$o->icon() ?? "fas fa-info") !!}
{!! $x ? $x->render() : sprintf('
',$o->icon() ?? "fas fa-info") !!}
{{ $dn }}
{{ Arr::get(Arr::get($o->getOriginal(),$key,['['.strtoupper(__('New Value')).']']),$xx) }}{{ Arr::get(Arr::get($o->getOriginal(),$key),$xx,'['.strtoupper(__('New Value')).']') }} {{ $y=Arr::get($value,$xx) }}