diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index 736401f..8db210b 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -11,10 +11,11 @@ use App\Classes\LDAP\Schema\AttributeType; /** * Represents an attribute of an LDAP Object */ -class Attribute implements \Countable, \ArrayAccess +class Attribute implements \Countable, \ArrayAccess, \Iterator { // Attribute Name protected string $name; + private int $counter = 0; protected ?AttributeType $schema = NULL; @@ -174,6 +175,31 @@ class Attribute implements \Countable, \ArrayAccess $this->values->push($value); } + public function current(): mixed + { + return $this->values->get($this->counter); + } + + public function next(): void + { + $this->counter++; + } + + public function key(): mixed + { + return $this->counter; + } + + public function valid(): bool + { + return $this->values->has($this->counter); + } + + public function rewind(): void + { + $this->counter = 0; + } + public function count(): int { return $this->values->count(); @@ -181,7 +207,7 @@ class Attribute implements \Countable, \ArrayAccess public function offsetExists(mixed $offset): bool { - return ! is_null($this->values->get($offset)); + return ! is_null($this->values->has($offset)); } public function offsetGet(mixed $offset): mixed diff --git a/app/Classes/LDAP/Attribute/Password.php b/app/Classes/LDAP/Attribute/Password.php index 33c1719..bacca92 100644 --- a/app/Classes/LDAP/Attribute/Password.php +++ b/app/Classes/LDAP/Attribute/Password.php @@ -4,7 +4,9 @@ namespace App\Classes\LDAP\Attribute; use Illuminate\Contracts\View\View; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; +use App\Classes\LDAP\Attribute\Password\Base; use App\Classes\LDAP\Attribute; use App\Traits\MD5Updates; @@ -14,6 +16,52 @@ use App\Traits\MD5Updates; final class Password extends Attribute { use MD5Updates; + private const password_helpers = 'Classes/LDAP/Attribute/Password'; + public const commands = 'App\\Classes\\LDAP\\Attribute\\Password\\'; + + private static function helpers(): Collection + { + $helpers = collect(); + + foreach (preg_grep('/^([^.])/',scandir(app_path(self::password_helpers))) as $file) { + if (($file === 'Base.php') || (! str_ends_with(strtolower($file),'.php'))) + continue; + + $class = self::commands.preg_replace('/\.php$/','',$file); + if ($helpers->count()) + $helpers->push(''); + + $helpers = $helpers + ->merge([$class::id()=>$class]); + } + + return $helpers; + } + + /** + * Return the object that will process a password + * + * @param string $id + * @return Base|null + */ + public static function hash(string $id): ?Attribute\Password\Base + { + return ($helpers=static::helpers())->has($id) ? new ($helpers->get($id)) : NULL; + } + + /** + * Given an LDAP password syntax {xxx}yyyyyy, this function will return xxx + * + * @param string $password + * @return string + */ + public static function hash_id(string $password): string + { + $m = []; + preg_match('/^{([A-Z]+)}(.*)$/',$password,$m); + + return Arr::get($m,1,'Clear'); + } public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View { @@ -21,7 +69,8 @@ final class Password extends Attribute ->with('o',$this) ->with('edit',$edit) ->with('old',$old) - ->with('new',$new); + ->with('new',$new) + ->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])); } public function render_item_old(int $key): ?string diff --git a/app/Classes/LDAP/Attribute/Password/Base.php b/app/Classes/LDAP/Attribute/Password/Base.php new file mode 100644 index 0000000..160466d --- /dev/null +++ b/app/Classes/LDAP/Attribute/Password/Base.php @@ -0,0 +1,14 @@ +render(); } + public function entry_password_check(Request $request) + { + $dn = Crypt::decryptString($request->dn); + $o = config('server')->fetch($dn); + + $password = $o->getObject('userpassword'); + + $result = collect(); + foreach ($password as $key => $value) { + $type = $password->hash_id($value); + $compare = Arr::get($request->password,$key); + //Log::debug(sprintf('comparing [%s] with [%s] type [%s]',$value,$compare,$type)); + + $result->push((($compare !== NULL) && Attribute\Password::hash($type)->compare($value,$compare)) ? 'OK' :'FAIL'); + } + + return $result; + } + /** * Show a confirmation to update a DN * @@ -103,9 +122,19 @@ class HomeController extends Controller $o = config('server')->fetch($dn); - foreach ($request->except(['_token','dn']) as $key => $value) + foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value) $o->{$key} = array_filter($value); + // We need to process and encrypt the password + $passwords = []; + foreach ($request->userpassword as $key => $value) { + if ($value) { + $type = Arr::get($request->userpassword_hash,$key); + array_push($passwords,Attribute\Password::hash($type)->encode($value)); + } + } + $o->userpassword = $passwords; + if (! $o->getDirty()) return back() ->withInput() diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 1b34fae..3006d58 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -52,11 +52,17 @@ class Entry extends Model /** * Determine if the new and old values for a given key are equivalent. + * + * @todo This function barfs on language tags, eg: key = givenname;lang-ja */ protected function originalIsEquivalent(string $key): bool { $key = $this->normalizeAttributeKey($key); + // @todo Silently ignore keys of language tags - we should work with them + if (str_contains($key,';')) + return TRUE; + return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key))) || (! $this->getObject($key)->isDirty()); } diff --git a/config/ldap.php b/config/ldap.php index 7ab1425..976daa1 100644 --- a/config/ldap.php +++ b/config/ldap.php @@ -153,13 +153,25 @@ return [ 'mail' => [ 'mail'=>[ 'sometimes', - 'array','min:1' + 'array', + 'min:1' ], 'mail.*' => [ 'nullable', 'email' ] ], + 'userpassword' => [ + 'userpassword' => [ + 'sometimes', + 'array', + 'min:1' + ], + 'userpassword.*' => [ + 'nullable', + 'min:8' + ] + ], 'uidnumber' => [ 'uidnumber' => [ 'sometimes', diff --git a/public/css/custom.css b/public/css/custom.css index 2a90b2d..eef973f 100644 --- a/public/css/custom.css +++ b/public/css/custom.css @@ -2,4 +2,14 @@ img.jpegphoto { display:block; max-width:100px; height:100px; +} + +/** ensure our userpassword has select is next to the password input */ +div#userpassword .select2-container--bootstrap-5 .select2-selection { + font-size: inherit; + border-bottom-right-radius: unset; + border-top-right-radius: unset; + width: 7em; + border: #444054 1px solid; + background-color: #f0f0f0; } \ No newline at end of file diff --git a/resources/views/components/attribute/layout.blade.php b/resources/views/components/attribute/layout.blade.php index f139668..e139abe 100644 --- a/resources/views/components/attribute/layout.blade.php +++ b/resources/views/components/attribute/layout.blade.php @@ -5,6 +5,6 @@ {{ $slot }} - + diff --git a/resources/views/components/attribute/objectclass.blade.php b/resources/views/components/attribute/objectclass.blade.php index d759df4..b62c7af 100644 --- a/resources/views/components/attribute/objectclass.blade.php +++ b/resources/views/components/attribute/objectclass.blade.php @@ -14,7 +14,7 @@ {{ $value }} @if ($o->isStructural($value)) - @lang('structural') + @lang('structural') @endif @endif diff --git a/resources/views/components/attribute/password.blade.php b/resources/views/components/attribute/password.blade.php index 5f29f84..4af4573 100644 --- a/resources/views/components/attribute/password.blade.php +++ b/resources/views/components/attribute/password.blade.php @@ -1,9 +1,9 @@ - - + @foreach($o->values as $value) @if($edit) - + + ($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ md5($value) }}" @readonly(true)> @@ -13,13 +13,17 @@ @else - {{ str_repeat('x',8) }} + {{ (($x=$o->hash_id($value)) && ($x !== 'Clear')) ? sprintf('{%s}',$x) : '' }}{{ str_repeat('x',8) }} @endif @endforeach + - @if($edit) - - @lang('Check Password') - - @endif - \ No newline at end of file +@if($edit) + + + + @lang('Check Password') + + + +@endif \ 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 17a1c08..067a0d8 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -3,23 +3,11 @@ @elseif($edit && $o->can_addvalues) (! $new)]) id="{{ $o->name_lc }}"> @lang('Add Value') - @if($new) - - @endif @endif @section('page-scripts') - @if(($edit && $o->can_addvalues)) + @if($edit && $o->can_addvalues)