Render HTML inputs for a DN with language tags - work for #16

This commit is contained in:
2025-04-05 11:38:07 +11:00
parent 633513d3e9
commit cf535286c5
25 changed files with 244 additions and 200 deletions

View File

@@ -7,6 +7,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Schema\AttributeType;
use App\Ldap\Entry;
/**
* Represents an attribute of an LDAP Object
@@ -100,9 +101,9 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
{
$this->dn = $dn;
$this->name = $name;
$this->values_old = collect($values);
$this->_values = collect($values);
$this->_values_old = collect($values);
$this->values = collect();
$this->oc = collect($oc);
$this->schema = (new Server)
@@ -149,15 +150,15 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// Used in Object Classes
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
// The current attribute values
'values' => $this->no_attr_tags ? collect($this->_values->first()) : $this->_values,
'values' => $this->no_attr_tags ? $this->tagValues() : $this->_values,
// The original attribute values
'values_old' => $this->no_attr_tags ? collect($this->_values_old->first()) : $this->_values_old,
'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old,
default => throw new \Exception('Unknown key:' . $key),
};
}
public function __set(string $key,mixed $values)
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
@@ -320,14 +321,14 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
->with('new',$new);
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
return Arr::get($this->values_old,$key);
return Arr::get($this->_values_old->dot(),$dotkey);
}
public function render_item_new(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
return Arr::get($this->values,$key);
return Arr::get($this->_values->dot(),$dotkey);
}
/**
@@ -343,17 +344,17 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
: collect();
}
public function tagValues(string $tag=''): Collection
public function tagValues(string $tag=Entry::TAG_NOTAG): Collection
{
return $this->_values
->filter(fn($item,$key)=>($key === $tag))
->values();
return collect($this->_values
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
}
public function tagValuesOld(string $tag=''): Collection
public function tagValuesOld(string $tag=Entry::TAG_NOTAG): Collection
{
return $this->_values_old
->filter(fn($item,$key)=>($key === $tag))
->values();
return collect($this->_values_old
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary;
use App\Ldap\Entry;
use App\Traits\MD5Updates;
/**
@@ -14,13 +15,14 @@ final class JpegPhoto extends Binary
{
use MD5Updates;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('f',new \finfo);
}
}

View File

@@ -26,18 +26,16 @@ final class KrbPrincipalKey extends Attribute
->with('new',$new);
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
$pw = Arr::get($this->values_old,$key);
return $pw
return parent::render_item_old($dotkey)
? str_repeat('*',16)
: NULL;
}
public function render_item_new(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
$pw = Arr::get($this->values,$key);
return $pw
return parent::render_item_new($dotkey)
? str_repeat('*',16)
: NULL;
}

View File

@@ -3,9 +3,9 @@
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use Illuminate\Support\Collection;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag

View File

@@ -29,19 +29,33 @@ final class ObjectClass extends Attribute
{
parent::__construct($dn,$name,$values,['top']);
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$this->values_old->contains($item->name));
$this->set_oc_schema($this->tagValuesOld());
}
public function __get(string $key): mixed
{
return match ($key) {
'structural' => $this->oc_schema->filter(fn($item) => $item->isStructural()),
'structural' => $this->oc_schema->filter(fn($item)=>$item->isStructural()),
default => parent::__get($key),
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
parent::__set($key,$values);
// We need to populate oc_schema, if we are a new OC and thus dont have any old values
if (! $this->values_old->count() && $this->values->count())
$this->set_oc_schema($this->tagValues());
break;
default: parent::__set($key,$values);
}
}
/**
* Is a specific value the structural objectclass
*
@@ -63,4 +77,11 @@ final class ObjectClass extends Attribute
->with('old',$old)
->with('new',$new);
}
private function set_oc_schema(Collection $tv): void
{
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$tv->contains($item->name));
}
}

View File

@@ -88,19 +88,23 @@ final class Password extends Attribute
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
}
public function render_item_old(int $key): ?string
public function render_item_old(string $dotkey): ?string
{
$pw = Arr::get($this->values_old,$key);
$pw = parent::render_item_old($dotkey);
return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
? (((($x=$this->hash($pw)) && ($x::id() === '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL;
}
public function render_item_new(int $key): ?string
public function render_item_new(string $dotkey): ?string
{
$pw = Arr::get($this->values,$key);
$pw = parent::render_item_new($dotkey);
return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL;
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Classes\LDAP\Export;
use Illuminate\Support\Str;
use App\Classes\LDAP\Export;
use App\Ldap\Entry;
/**
* Export from LDAP using an LDIF format
@@ -55,8 +56,8 @@ class LDIF extends Export
foreach ($tagvalues as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
? sprintf('%s: %s',$ao->name.($tag ? ';'.$tag : ''),$value)
: sprintf('%s:: %s',$ao->name.($tag ? ';'.$tag : ''),base64_encode($value))
? sprintf('%s: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),$value)
: sprintf('%s:: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),base64_encode($value))
,$this->br);
}
}

View File

@@ -58,10 +58,10 @@ class HomeController extends Controller
$o = new Entry;
if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
$o->objectclass = $x;
$o->objectclass = [Entry::TAG_NOTAG=>$x];
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->{$ao->name} = '';
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$o->setRDNBase($key['dn']);
}
@@ -188,8 +188,6 @@ class HomeController extends Controller
$result = (new Entry)
->query()
//->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
//->select(['*'])
->setDn($dn)
->recursive()
->get();

View File

@@ -23,6 +23,7 @@ class Entry extends Model
{
private const TAG_CHARS = 'a-zA-Z0-9-';
private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']';
public const TAG_NOTAG = '_null_';
// Our Attribute objects
private Collection $objects;
@@ -53,8 +54,9 @@ class Entry extends Model
/**
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes.
*
* This returns an array that should be consistent with $this->attributes
*
* @return array
* @note $this->attributes may not be updated with changes
*/
public function getAttributes(): array
{
@@ -63,7 +65,7 @@ class Entry extends Model
($item->no_attr_tags)
? [strtolower($item->name)=>$item->values]
: $item->values
->flatMap(fn($v,$k)=>[strtolower($item->name.($k ? ';'.$k : ''))=>$v]))
->flatMap(fn($v,$k)=>[strtolower($item->name.($k !== self::TAG_NOTAG ? ';'.$k : ''))=>$v]))
->toArray();
}
@@ -76,12 +78,10 @@ class Entry extends Model
{
$key = $this->normalizeAttributeKey($key);
// @todo Silently ignore keys of language tags - we should work with them
if (str_contains($key,';'))
return TRUE;
list($attribute,$tag) = $this->keytag($key);
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key)))
|| (! $this->getObject($key)->isDirty());
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute)))
|| (! $this->getObject($attribute)->isDirty());
}
public static function query(bool $noattrs=false): Builder
@@ -98,18 +98,22 @@ class Entry extends Model
* As attribute values are updated, or new ones created, we need to mirror that
* into our $objects. This is called when we $o->key = $value
*
* This function should update $this->attributes and correctly reflect changes in $this->objects
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function setAttribute(string $key,mixed $value): static
{
parent::setAttribute($key,$value);
foreach ($value as $k => $v)
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
$key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($key) ?: Factory::create($this->dn ?: '',$key,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($this->attributes[$key]);
$o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($value);
$this->objects->put($key,$o);
@@ -173,21 +177,13 @@ class Entry extends Model
$key = $this->normalizeAttributeKey(strtolower($key));
// If the attribute name has tags
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = '';
}
list($attribute,$tag) = $this->keytag($key);
if (! config('server')->schema('attributetypes')->has($attribute))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]);
$o->addValue($tags,$value);
$o->addValue($tag,[$value]);
$this->objects->put($attribute,$o);
}
@@ -203,16 +199,7 @@ class Entry extends Model
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attrtag => $values) {
// If the attribute name has tags
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$attrtag,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $attrtag;
$tags = NULL;
}
list($attribute,$tags) = $this->keytag($attrtag);
$orig = Arr::get($this->original,$attrtag,[]);
@@ -227,7 +214,8 @@ class Entry extends Model
$entry_oc,
));
$o->values = $o->values->merge([$tags=>$values]);
$o->addValue($tags,$values);
$o->addValueOld($tags,Arr::get($this->original,$attrtag));
$result->put($attribute,$o);
}
@@ -270,7 +258,7 @@ class Entry extends Model
{
$result = collect();
foreach ($this->objectclass as $oc)
foreach ($this->getObject('objectclass')->values as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
return $result;
@@ -369,7 +357,8 @@ class Entry extends Model
->filter(fn($item)=>
$item && collect(explode(';',$item))->filter(
fn($item)=>
(! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
(! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item))
&& (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
&& (! preg_match('/^binary$/',$item))
)
->count())
@@ -381,6 +370,9 @@ 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
{
@@ -401,12 +393,14 @@ class Entry extends Model
/**
* Return this list of user attributes
*
* @param string|null $tag If null return all tags
* @return Collection
*/
public function getVisibleAttributes(): Collection
public function getVisibleAttributes(?string $tag=NULL): Collection
{
return $this->objects
->filter(fn($item)=>! $item->is_internal);
->filter(fn($item)=>! $item->is_internal)
->filter(fn($item)=>is_null($tag) || count($item->tagValues($tag)) > 0);
}
public function hasAttribute(int|string $key): bool
@@ -452,65 +446,92 @@ class Entry extends Model
*/
public function icon(): string
{
$objectclasses = array_map('strtolower',$this->objectclass);
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value
if (in_array('person',$objectclasses) ||
in_array('organizationalperson',$objectclasses) ||
in_array('inetorgperson',$objectclasses) ||
in_array('account',$objectclasses) ||
in_array('posixaccount',$objectclasses))
if ($objectclasses->intersect([
'account',
'inetorgperson',
'organizationalperson',
'person',
'posixaccount',
])->count())
return 'fas fa-user';
elseif (in_array('organization',$objectclasses))
elseif ($objectclasses->contains('organization'))
return 'fas fa-university';
elseif (in_array('organizationalunit',$objectclasses))
elseif ($objectclasses->contains('organizationalunit'))
return 'fas fa-object-group';
elseif (in_array('posixgroup',$objectclasses) ||
in_array('groupofnames',$objectclasses) ||
in_array('groupofuniquenames',$objectclasses) ||
in_array('group',$objectclasses))
elseif ($objectclasses->intersect([
'posixgroup',
'groupofnames',
'groupofuniquenames',
'group',
])->count())
return 'fas fa-users';
elseif (in_array('dcobject',$objectclasses) ||
in_array('domainrelatedobject',$objectclasses) ||
in_array('domain',$objectclasses) ||
in_array('builtindomain',$objectclasses))
elseif ($objectclasses->intersect([
'dcobject',
'domainrelatedobject',
'domain',
'builtindomain',
])->count())
return 'fas fa-network-wired';
elseif (in_array('alias',$objectclasses))
elseif ($objectclasses->contains('alias'))
return 'fas fa-theater-masks';
elseif (in_array('country',$objectclasses))
elseif ($objectclasses->contains('country'))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0)));
elseif (in_array('device',$objectclasses))
elseif ($objectclasses->contains('device'))
return 'fas fa-mobile-alt';
elseif (in_array('document',$objectclasses))
elseif ($objectclasses->contains('document'))
return 'fas fa-file-alt';
elseif (in_array('iphost',$objectclasses))
elseif ($objectclasses->contains('iphost'))
return 'fas fa-wifi';
elseif (in_array('room',$objectclasses))
elseif ($objectclasses->contains('room'))
return 'fas fa-door-open';
elseif (in_array('server',$objectclasses))
elseif ($objectclasses->contains('server'))
return 'fas fa-server';
elseif (in_array('openldaprootdse',$objectclasses))
elseif ($objectclasses->contains('openldaprootdse'))
return 'fas fa-info';
// Default
return 'fa-fw fas fa-cog';
}
/**
* Given an LDAP attribute, this will return the attribute name and the tag
* eg: description;lang-cn will return [description,lang-cn]
*
* @param string $key
* @return array
*/
private function keytag(string $key): array
{
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = self::TAG_NOTAG;
}
return [$attribute,$tags];
}
/**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*

View File

@@ -5,6 +5,7 @@ namespace App\View\Components;
use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class Attribute extends Component
{
@@ -15,29 +16,29 @@ class Attribute extends Component
public string $langtag;
public ?string $na; // Text to render if the LDAPAttribute is null
/**
* Create a new component instance.
*/
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag='',?string $na=NULL)
{
/**
* Create a new component instance.
*/
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG,?string $na=NULL)
{
$this->o = $o;
$this->edit = $edit;
$this->old = $old;
$this->new = $new;
$this->langtag = $langtag;
$this->na = $na;
}
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return $this->o
? $this->o
->render($this->edit,$this->old,$this->new)
: $this->na;
}
}
}

View File

@@ -4,10 +4,10 @@ namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute;
use App\Ldap\Entry;
class AttributeType extends Component
{
@@ -18,7 +18,7 @@ class AttributeType extends Component
/**
* Create a new component instance.
*/
public function __construct(LDAPAttribute $o,bool $new=FALSE,string $langtag='')
public function __construct(LDAPAttribute $o,bool $new=FALSE,string $langtag=Entry::TAG_NOTAG)
{
$this->o = $o;
$this->new = $new;