phpldapadmin/app/Ldap/Entry.php

423 lines
10 KiB
PHP

<?php
namespace App\Ldap;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Crypt;
use LdapRecord\Support\Arr;
use LdapRecord\Models\Model;
use LdapRecord\Query\Model\Builder;
use App\Classes\LDAP\Attribute;
use App\Classes\LDAP\Attribute\Factory;
use App\Classes\LDAP\Export\LDIF;
use App\Exceptions\Import\AttributeException;
class Entry extends Model
{
private Collection $objects;
private bool $noObjectAttributes = FALSE;
/* OVERRIDES */
public function __construct(array $attributes = [])
{
$this->objects = collect();
parent::__construct($attributes);
}
public function discardChanges(): static
{
parent::discardChanges();
// If we are discharging changes, we need to reset our $objects;
$this->objects = $this->getAttributesAsObjects($this->attributes);
return $this;
}
/**
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes.
*
* @return array
* @note $this->attributes may not be updated with changes
*/
public function getAttributes(): array
{
return $this->objects->map(function($item) { return $item->values->toArray(); })->toArray();
}
/**
* Determine if the new and old values for a given key are equivalent.
*/
protected function originalIsEquivalent(string $key): bool
{
$key = $this->normalizeAttributeKey($key);
if ((! array_key_exists($key, $this->original)) && (! $this->objects->has($key))) {
return TRUE;
}
$current = $this->attributes[$key];
$original = $this->objects->get($key)->values;
if ($current === $original) {
return true;
}
return ! $this->getObject($key)->isDirty();
}
public static function query(bool $noattrs=false): Builder
{
$o = new static;
if ($noattrs)
$o->noObjectAttributes();
return $o->newQuery();
}
/**
* As attribute values are updated, or new ones created, we need to mirror that
* into our $objects
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function setAttribute(string $key, mixed $value): static
{
parent::setAttribute($key,$value);
$key = $this->normalizeAttributeKey($key);
if ((! $this->objects->get($key)) && $value) {
$o = new Attribute($key,[]);
$o->value = $value;
$this->objects->put($key,$o);
} elseif ($this->objects->get($key)) {
$this->objects->get($key)->value = $this->attributes[$key];
}
return $this;
}
/**
* We'll shadow $this->attributes to $this->objects - a collection of Attribute objects
*
* Using the objects, it'll make it easier to work with attribute values
*
* @param array $attributes
* @return $this
*/
public function setRawAttributes(array $attributes = []): static
{
parent::setRawAttributes($attributes);
// We only set our objects on DN entries (otherwise we might get into a recursion loop if this is the schema DN)
if ($this->dn && (! in_array($this->dn,Arr::get($this->attributes,'subschemasubentry',[])))) {
$this->objects = $this->getAttributesAsObjects($this->attributes);
} else {
$this->objects = collect();
}
return $this;
}
/* 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 */
public function addAttribute(string $key,mixed $value): void
{
$key = $this->normalizeAttributeKey($key);
if (config('server')->schema('attributetypes')->has($key) === FALSE)
throw new AttributeException('Schema doesnt have attribute [%s]',$key);
if ($x=$this->objects->get($key)) {
$x->addValue($value);
} else {
$this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($value)));
}
}
/**
* Convert all our attribute values into an array of Objects
*
* @param array $attributes
* @return Collection
*/
protected function getAttributesAsObjects(array $attributes): Collection
{
$result = collect();
foreach ($attributes 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')));
// Store our original value to know if this attribute has changed
if ($x=Arr::get($this->original,$attribute))
$o->oldValues($x);
$result->put($attribute,$o);
}
}
$sort = collect(config('ldap.attr_display_order',[]))->transform(function($item) { return strtolower($item); });
// Order the attributes
$result = $result->sortBy([function(Attribute $a,Attribute $b) use ($sort): int {
if ($a === $b)
return 0;
// Check if $a/$b are in the configuration to be sorted first, if so get it's key
$a_key = $sort->search($a->name_lc);
$b_key = $sort->search($b->name_lc);
// If the keys were not in the sort list, set the key to be the count of elements (ie: so it is last to be sorted)
if ($a_key === FALSE)
$a_key = $sort->count()+1;
if ($b_key === FALSE)
$b_key = $sort->count()+1;
// Case where neither $a, nor $b are in ldap.attr_display_order, $a_key = $b_key = one greater than num elements.
// So we sort them alphabetically
if ($a_key === $b_key)
return strcasecmp($a->name,$b->name);
// 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;
} ]);
return $result;
}
/**
* Return a list of available attributes - as per the objectClass entry of the record
*
* @return Collection
*/
public function getAvailableAttributes(): Collection
{
$result = collect();
foreach ($this->objectclass as $oc)
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
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
*
* @return Collection
*/
public function getInternalAttributes(): Collection
{
return $this->objects->filter(function($item) {
return $item->is_internal;
});
}
/**
* Get an attribute as an object
*
* @param string $key
* @return Attribute|null
*/
public function getObject(string $key): Attribute|null
{
return $this->objects->get($this->normalizeAttributeKey($key));
}
public function getObjects(): Collection
{
// In case we havent built our objects yet (because they werent available while determining the schema DN)
if ((! $this->objects->count()) && $this->attributes)
$this->objects = $this->getAttributesAsObjects($this->attributes);
return $this->objects;
}
/**
* Return a list of attributes without any values
*
* @return Collection
*/
public function getMissingAttributes(): Collection
{
return $this->getAvailableAttributes()->diff($this->getVisibleAttributes());
}
/**
* Return this list of user attributes
*
* @return Collection
*/
public function getVisibleAttributes(): Collection
{
return $this->objects->filter(function($item) {
return ! $item->is_internal;
});
}
public function hasAttribute(int|string $key): bool
{
return $this->objects->has($key);
}
/**
* Export this record
*
* @param string $method
* @param string $scope
* @return string
* @throws \Exception
*/
public function export(string $method,string $scope): string
{
// @todo To implement
switch ($scope) {
case 'base':
case 'one':
case 'sub':
break;
default:
throw new \Exception('Export scope unknown:'.$scope);
}
switch ($method) {
case 'ldif':
return new LDIF(collect($this));
default:
throw new \Exception('Export method not implemented:'.$method);
}
}
/**
* Return an icon for a DN based on objectClass
*
* @return string
*/
public function icon(): string
{
$objectclasses = array_map('strtolower',$this->objectclass);
// 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))
return 'fas fa-user';
elseif (in_array('organization',$objectclasses))
return 'fas fa-university';
elseif (in_array('organizationalunit',$objectclasses))
return 'fas fa-object-group';
elseif (in_array('posixgroup',$objectclasses) ||
in_array('groupofnames',$objectclasses) ||
in_array('groupofuniquenames',$objectclasses) ||
in_array('group',$objectclasses))
return 'fas fa-users';
elseif (in_array('dcobject',$objectclasses) ||
in_array('domainrelatedobject',$objectclasses) ||
in_array('domain',$objectclasses) ||
in_array('builtindomain',$objectclasses))
return 'fas fa-network-wired';
elseif (in_array('alias',$objectclasses))
return 'fas fa-theater-masks';
elseif (in_array('country',$objectclasses))
return sprintf('flag %s',strtolower(Arr::get($this->c,0)));
elseif (in_array('device',$objectclasses))
return 'fas fa-mobile-alt';
elseif (in_array('document',$objectclasses))
return 'fas fa-file-alt';
elseif (in_array('iphost',$objectclasses))
return 'fas fa-wifi';
elseif (in_array('room',$objectclasses))
return 'fas fa-door-open';
elseif (in_array('server',$objectclasses))
return 'fas fa-server';
elseif (in_array('openldaprootdse',$objectclasses))
return 'fas fa-info';
// Default
return 'fa-fw fas fa-cog';
}
/**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*
* @return $this
*/
public function noObjectAttributes(): static
{
$this->noObjectAttributes = TRUE;
return $this;
}
}