2023-03-15 02:33:27 +01:00
|
|
|
<?php
|
|
|
|
namespace EesyLDAP;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property string|false $dn
|
|
|
|
* @property-read array<string,array<string>> $changes
|
|
|
|
*/
|
|
|
|
class Entry {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Entry DN
|
|
|
|
* @var string|false
|
|
|
|
*/
|
|
|
|
protected $dn = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attributes data
|
|
|
|
* @var array<string,array<string>>
|
|
|
|
*/
|
|
|
|
protected $data = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changed data
|
2023-03-21 16:55:46 +01:00
|
|
|
* @var array{add: array<string,array<int,string>>, replace: array<string,array<int,string>>, delete: array<string,array<int,string>>}
|
2023-03-15 02:33:27 +01:00
|
|
|
*/
|
2023-03-21 16:55:46 +01:00
|
|
|
protected $changes = array(
|
|
|
|
'add' => array(),
|
|
|
|
'replace' => array(),
|
|
|
|
'delete' => array(),
|
|
|
|
);
|
2023-03-15 02:33:27 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* LDAP connection
|
|
|
|
* @var Ldap|null
|
|
|
|
*/
|
|
|
|
protected $ldap = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
* @param string|null $dn Object DN (optional, default=null)
|
|
|
|
* @param array<string,array<string>> $data Object attributes data (optional, default=null)
|
|
|
|
* @param Ldap|null $ldap The LDAP connection (optional, default=null)
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function __construct($dn=null, $data=null, $ldap=null) {
|
|
|
|
if (is_string($dn))
|
|
|
|
$this -> dn = $dn;
|
|
|
|
if (is_array($data))
|
|
|
|
foreach($data as $attr => $value)
|
|
|
|
$this -> data[mb_strtolower($attr)] = $data[$attr];
|
|
|
|
$this -> ldap = $ldap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get entry key
|
|
|
|
* @param string $key
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function __get($key) {
|
|
|
|
switch ($key) {
|
|
|
|
case 'dn':
|
|
|
|
return $this -> dn;
|
|
|
|
case 'changes':
|
|
|
|
return $this -> changes;
|
|
|
|
default:
|
2023-03-17 20:02:52 +01:00
|
|
|
return $this -> get_value($key);
|
2023-03-15 02:33:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set entry key value
|
|
|
|
* @param string $key
|
|
|
|
* @param mixed $value
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function __set($key, $value) {
|
|
|
|
switch ($key) {
|
|
|
|
case 'dn':
|
|
|
|
if (is_string($value) || $value === false)
|
|
|
|
$this -> dn = $value;
|
|
|
|
else
|
|
|
|
$this -> error('Unexcepted DN value provided: must be a string or false');
|
|
|
|
default:
|
2023-03-17 20:02:52 +01:00
|
|
|
$this -> set_value($key, $value);
|
2023-03-15 02:33:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if entry key is set
|
|
|
|
* @param string $key
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function __isset($key) {
|
2023-03-21 16:55:46 +01:00
|
|
|
if (in_array($key, array('dn', 'changes')))
|
|
|
|
return true;
|
|
|
|
return $this->attribute_is_set($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if entry attribute is set
|
|
|
|
* @param string $attr
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function attribute_is_set($attr) {
|
|
|
|
$attr = strtolower($attr);
|
|
|
|
// Attribute defined in object data and not deleted
|
|
|
|
if (
|
|
|
|
array_key_exists($attr, $this -> data)
|
|
|
|
&& (
|
|
|
|
!array_key_exists($attr, $this -> changes['delete'])
|
|
|
|
|| $this -> data[$attr] != $this -> changes['delete'][$attr]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Note: Do not need to to replace changes, because we only replace value on pre-existing attribute
|
|
|
|
|
|
|
|
// Attribute have been added
|
|
|
|
if (array_key_exists($attr, $this -> changes['add']))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the entry could have the specified attribute against the LDAP schema (if loaded)
|
|
|
|
* @param string $attr
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function has_attribute($attr) {
|
|
|
|
if (!$this -> ldap || !$this -> ldap -> schema)
|
|
|
|
return true;
|
|
|
|
$objectclasses = $this -> get_raw_values('objectclass');
|
|
|
|
if (!$objectclasses || !is_array($objectclasses))
|
|
|
|
return false;
|
|
|
|
return $this -> ldap -> schema -> has_attribute($attr, $objectclasses);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get raw attribute values
|
|
|
|
* @param string $attr The expected attribute name
|
|
|
|
* @return array<int,string>
|
|
|
|
*/
|
|
|
|
protected function _get_raw_values($attr) {
|
|
|
|
$attr = mb_strtolower($attr);
|
|
|
|
if (array_key_exists($attr, $this -> changes['replace']))
|
|
|
|
return $this -> changes['replace'][$attr];
|
|
|
|
$values = array_key_exists($attr, $this -> data)?$this -> data[$attr]:array();
|
|
|
|
if (array_key_exists($attr, $this -> changes['add']))
|
|
|
|
$values = array_merge($values, $this -> changes['add'][$attr]);
|
|
|
|
if (array_key_exists($attr, $this -> changes['delete'])) {
|
|
|
|
foreach ($this -> changes['delete'][$attr] as $value)
|
|
|
|
if (($key = array_search($value, $values)) !== false)
|
|
|
|
unset($values[$key]);
|
|
|
|
}
|
|
|
|
return $values;
|
2023-03-15 02:33:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-03-17 20:02:52 +01:00
|
|
|
* Get raw attribute values
|
2023-03-15 02:33:27 +01:00
|
|
|
* @param string $attr The expected attribute name
|
|
|
|
* @param mixed $default The default value if attribute is not set
|
|
|
|
* (optional, default: null or array() if $all_values is True)
|
|
|
|
* @param bool $all_values If True, return all the attribute values, otherwise return only the
|
|
|
|
* first one (optional, default: true)
|
2023-03-16 02:21:46 +01:00
|
|
|
* @return ( $all_values is True ? array<string> : mixed )
|
2023-03-15 02:33:27 +01:00
|
|
|
*/
|
2023-03-17 20:02:52 +01:00
|
|
|
public function get_raw_values($attr, $default=null, $all_values=true) {
|
2023-03-21 16:55:46 +01:00
|
|
|
$values = $this -> _get_raw_values($attr);
|
2023-03-15 02:33:27 +01:00
|
|
|
if (!$values)
|
|
|
|
return is_null($default) && $all_values?array():$default;
|
|
|
|
return $all_values?$values:$values[0];
|
|
|
|
}
|
|
|
|
|
2023-03-17 20:02:52 +01:00
|
|
|
/**
|
|
|
|
* Get an attribute value (using LDAP schema if used and a LDAP link is registered on entry)
|
|
|
|
* @param string $attr The expected attribute name
|
|
|
|
* @param mixed $default The default value if attribute is not set
|
|
|
|
* (optional, default: null or array() if $all_values is True)
|
|
|
|
* @param bool $first If True, return only the first attribute values, otherwise return
|
|
|
|
* depending on the fact is single valued or not in LDAP schema (optional,
|
|
|
|
* default: false)
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function get_value($attr, $default=null, $first=false) {
|
|
|
|
if (!$this -> ldap || !$this -> ldap -> schema)
|
|
|
|
return $this -> get_raw_values($attr, $default, $first?false:true);
|
2023-03-21 16:55:46 +01:00
|
|
|
$values = $this -> _get_raw_values($attr);
|
2023-03-17 20:02:52 +01:00
|
|
|
$attribute = $this -> ldap -> schema -> attribute($attr);
|
|
|
|
if (!$attribute)
|
|
|
|
return $this -> error('Unkown attribute %s in schema', null, $attr);
|
|
|
|
if (!$values) {
|
|
|
|
if (is_null($default) && !$first && !$attribute->single)
|
|
|
|
return array();
|
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
return $attribute -> ldap2php($values);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set raw LDAP attribute values
|
|
|
|
* @param string $attr
|
|
|
|
* @param mixed $values
|
2023-03-21 16:55:46 +01:00
|
|
|
* @return bool
|
2023-03-17 20:02:52 +01:00
|
|
|
*/
|
|
|
|
public function set_raw_values($attr, $values) {
|
2023-03-21 16:55:46 +01:00
|
|
|
return $this -> replace($attr, $values, true);
|
2023-03-17 20:02:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set LDAP attribute values
|
|
|
|
* @param string $attr
|
|
|
|
* @param mixed $value
|
2023-03-21 16:55:46 +01:00
|
|
|
* @return bool
|
2023-03-17 20:02:52 +01:00
|
|
|
*/
|
|
|
|
public function set_value($attr, $value) {
|
2023-03-21 16:55:46 +01:00
|
|
|
return $this -> replace($attr, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert attribute PHP value to LDAP values
|
|
|
|
* @param string $attr The attribute name
|
|
|
|
* @param mixed $value
|
|
|
|
* @param bool $one_value Convert one value of the attribute (optional, default: false)
|
|
|
|
* @return ( $one_value is True ? string|null : array<int,string>|false )
|
|
|
|
*/
|
|
|
|
public function php2ldap($attr, $value, $one_value=false) {
|
2023-03-17 20:02:52 +01:00
|
|
|
if (!$this -> ldap || !$this -> ldap -> schema) {
|
2023-03-21 16:55:46 +01:00
|
|
|
// No LDAP schema available
|
2023-03-26 16:33:56 +02:00
|
|
|
if ($one_value) return is_null($value)?null:strval($value);
|
2023-03-21 16:55:46 +01:00
|
|
|
if (is_null($value)) return array();
|
2023-03-26 16:33:56 +02:00
|
|
|
return array_map('strval', is_array($value)?$value:array($value));
|
2023-03-17 20:02:52 +01:00
|
|
|
}
|
2023-03-21 16:55:46 +01:00
|
|
|
|
|
|
|
// Convert using LDAP schema
|
2023-03-17 20:02:52 +01:00
|
|
|
$attribute = $this -> ldap -> schema -> attribute($attr);
|
|
|
|
if (!$attribute)
|
|
|
|
return $this -> error('Unkown attribute %s in schema', null, $attr);
|
2023-03-21 16:55:46 +01:00
|
|
|
return $attribute -> php2ldap($value, $one_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an attribute value
|
|
|
|
* @param string $attr The attribute name
|
|
|
|
* @param mixed $value The value to add. If it's an array, consider as multiple value to add
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function add($attr, $value) {
|
|
|
|
if (!$this -> has_attribute($attr))
|
|
|
|
return $this -> error('%s: This entry could not have the attribute %s', $this, $attr);
|
|
|
|
if (is_array($value)) {
|
|
|
|
foreach($value as $v)
|
|
|
|
if (!$this -> add($attr, $v))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
$attr = mb_strtolower($attr);
|
|
|
|
$value = $this -> php2ldap($attr, $value, true);
|
|
|
|
if (is_null($value))
|
|
|
|
return $this -> error('%s: Could not add null value the attribute %s', $this, $attr);
|
|
|
|
|
|
|
|
// If we already do a replace on this attribute, add it in this attribute
|
|
|
|
if (isset($this -> changes['replace'][$attr])) {
|
|
|
|
if (in_array($value, $this -> changes['replace'][$attr]))
|
|
|
|
return true;
|
|
|
|
$this -> changes['replace'][$attr][] = $value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we already have a delete operation on this attribute, check this value was not deleted
|
|
|
|
if (isset($this -> changes['delete'][$attr])) {
|
|
|
|
if (($key = array_search($value, $this -> changes['delete'][$attr])) !== false) {
|
|
|
|
unset($this -> changes['delete'][$attr][$key]);
|
|
|
|
if (empty($this -> changes['delete'][$attr]))
|
|
|
|
unset($this -> changes['delete'][$attr]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if this value is not already in the attribute data
|
|
|
|
if (isset($this -> data[$attr]) && in_array($value, $this -> data[$attr]))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Check if this value was not already added
|
|
|
|
if (isset($this -> changes['add'][$attr])) {
|
|
|
|
if (in_array($value, $this -> changes['add'][$attr]))
|
|
|
|
return true;
|
|
|
|
$this -> changes['add'][$attr][] = $value;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
$this -> changes['add'][$attr] = array($value);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete an attribute value
|
|
|
|
* @param string $attr The attribute name
|
|
|
|
* @param mixed $value The value to delete. If it's an array, consider as multiple value to delete.
|
|
|
|
* If not provided or null, delete all the attribute name.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function delete($attr, $value=null) {
|
|
|
|
if (!$this -> has_attribute($attr))
|
|
|
|
return $this -> error('%s: This entry could not have the attribute %s', $this, $attr);
|
|
|
|
|
|
|
|
$attr = mb_strtolower($attr);
|
|
|
|
|
|
|
|
// If null value provided, delete all attribute values
|
|
|
|
if (is_null($value)) {
|
|
|
|
// Remove all previous operations on this attribute
|
|
|
|
if (isset($this -> changes['replace'][$attr]))
|
|
|
|
unset($this -> changes['replace'][$attr]);
|
|
|
|
if (isset($this -> changes['add'][$attr]))
|
|
|
|
unset($this -> changes['add'][$attr]);
|
|
|
|
if (isset($this -> changes['delete'][$attr]))
|
|
|
|
unset($this -> changes['delete'][$attr]);
|
|
|
|
// If attribute originally have data, delete all of it
|
|
|
|
if (isset($this -> data[$attr]))
|
|
|
|
$this -> changes['delete'][$attr] = $this -> data[$attr];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$values = $this -> php2ldap($attr, $value);
|
|
|
|
if ($values === false)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
foreach($values as $value) {
|
|
|
|
// If we already do a replace operation on this attribute, delete the value in the replaced value
|
|
|
|
if (isset($this -> changes['replace'][$attr])) {
|
|
|
|
if (($key = array_search($value, $this -> changes['replace'][$attr])) !== false) {
|
|
|
|
// Value detected in replaced value, delete if
|
|
|
|
unset($this -> changes['replace'][$attr][$key]);
|
|
|
|
// Check if we still have replace values
|
|
|
|
if (empty($this -> changes['replace'][$attr])) {
|
|
|
|
// No replaced value, consider the operation as a deletion
|
|
|
|
unset($this -> changes['replace'][$attr]);
|
|
|
|
if (isset($this -> data[$attr]))
|
|
|
|
$this -> changes['delete'][$attr] = $this -> data[$attr];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we already have a delete operation on this attribute, check this value was not already deleted
|
|
|
|
if (isset($this -> changes['delete'][$attr]) && in_array($value, $this-> changes['delete'][$attr]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check this value is in the attribute data
|
|
|
|
if (!isset($this -> data[$attr]) || !in_array($value, $this -> data[$attr]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (isset($this -> changes['delete'][$attr]))
|
|
|
|
$this -> changes['delete'][$attr][] = $value;
|
|
|
|
else
|
|
|
|
$this -> changes['delete'][$attr] = array($value);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replace an attribute value
|
|
|
|
* @param string $attr The attribute name
|
|
|
|
* @param mixed $value The new attribute value to delete. If null, delete all the attribute name.
|
|
|
|
* @param bool $consider_as_raw_values If true, consider provided value as raw (optional, default: false)
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function replace($attr, $value, $consider_as_raw_values=false) {
|
|
|
|
if (!$this -> has_attribute($attr))
|
|
|
|
return $this -> error('%s: This entry could not have the attribute %s', $this, $attr);
|
|
|
|
$attr = mb_strtolower($attr);
|
|
|
|
|
|
|
|
// If raw values provied, just be sure it's an array of string
|
|
|
|
if ($consider_as_raw_values) {
|
|
|
|
$value = Util::ensure_array_of_string($value);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Convert PHP value to LDAP values
|
|
|
|
$value = $this -> php2ldap($attr, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If empty value, delete the attribute
|
|
|
|
if (empty($value))
|
|
|
|
return $this -> delete($attr);
|
|
|
|
|
|
|
|
// Remove all previous add/delete operations on this attribute
|
|
|
|
if (isset($this -> changes['add'][$attr]))
|
|
|
|
unset($this -> changes['add'][$attr]);
|
|
|
|
if (isset($this -> changes['delete'][$attr]))
|
|
|
|
unset($this -> changes['delete'][$attr]);
|
|
|
|
if (isset($this -> changes['replace'][$attr]))
|
|
|
|
unset($this -> changes['replace'][$attr]);
|
|
|
|
|
|
|
|
if (!isset($this->data[$attr]))
|
|
|
|
$this -> changes['add'][$attr] = $value;
|
|
|
|
elseif ($value != $this->data[$attr])
|
|
|
|
$this -> changes['replace'][$attr] = $value;
|
|
|
|
return true;
|
2023-03-17 20:02:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-15 02:33:27 +01:00
|
|
|
/**
|
|
|
|
* Log and eventually raise an error
|
|
|
|
* @param string $error The error message
|
|
|
|
* @param array<mixed> $extra_args If passed, will be used to compute the error message using sprintf
|
|
|
|
* @return false
|
|
|
|
* @throws LdapException
|
|
|
|
*/
|
|
|
|
protected function error($error, ...$extra_args) {
|
|
|
|
if ($extra_args)
|
|
|
|
$error = call_user_func_array('sprintf', array_merge(array($error), $extra_args));
|
|
|
|
// Note: sprintf always return string
|
|
|
|
if ($this->ldap)
|
|
|
|
// @phpstan-ignore-next-line
|
|
|
|
return $this->ldap->error($error);
|
|
|
|
throw new LdapException($error); // @phpstan-ignore-line
|
|
|
|
}
|
2023-03-21 16:55:46 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Magic method to compute string representation of this schema entry object
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function __toString() {
|
|
|
|
if ($this -> dn)
|
|
|
|
return sprintf('<LDAP Entry "%s">', $this->dn);
|
|
|
|
return '<New LDAP Entry>';
|
|
|
|
}
|
2023-03-15 02:33:27 +01:00
|
|
|
}
|