From bb68b10ec4512ae3334440cb76c09a10b01c4a1b Mon Sep 17 00:00:00 2001 From: Benjamin Renard Date: Fri, 17 Mar 2023 20:02:52 +0100 Subject: [PATCH] Use schema Attribute in Entry to convert value from/to LDAP from/to PHP --- src/Entry.php | 80 +++++++++++++++++++---- src/Ldap.php | 10 +-- src/Schema.php | 28 +++++++- src/Schema/Attribute.php | 54 +++++++++++++++ src/Schema/Attribute/BooleanAttribute.php | 68 +++++++++++++++++++ src/Schema/SchemaEntry.php | 18 +++-- 6 files changed, 236 insertions(+), 22 deletions(-) create mode 100644 src/Schema/Attribute/BooleanAttribute.php diff --git a/src/Entry.php b/src/Entry.php index ab91ab3..8e71c09 100644 --- a/src/Entry.php +++ b/src/Entry.php @@ -60,7 +60,7 @@ class Entry { case 'changes': return $this -> changes; default: - return $this -> get_values($key); + return $this -> get_value($key); } } @@ -78,14 +78,7 @@ class Entry { else $this -> error('Unexcepted DN value provided: must be a string or false'); default: - $key = mb_strtolower($key); - if (is_null($value)) - $value = array(); - if (!is_array($value)) - $value = array($value); - $this -> changes[$key] = array(); - foreach($value as $v) - $this -> changes[$key][] = strval($v); + $this -> set_value($key, $value); } } @@ -103,7 +96,7 @@ class Entry { } /** - * Get an attribute values + * Get raw attribute values * @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) @@ -111,7 +104,7 @@ class Entry { * first one (optional, default: true) * @return ( $all_values is True ? array : mixed ) */ - public function get_values($attr, $default=null, $all_values=true) { + public function get_raw_values($attr, $default=null, $all_values=true) { $attr = mb_strtolower($attr); $values = false; if (array_key_exists($attr, $this -> changes)) @@ -123,6 +116,71 @@ class Entry { return $all_values?$values:$values[0]; } + /** + * 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); + $attr = mb_strtolower($attr); + $values = false; + if (array_key_exists($attr, $this -> changes)) + $values = &$this -> changes[$attr]; + elseif (array_key_exists($attr, $this -> data)) + $values = &$this -> data[$attr]; + $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 + * @return void + */ + public function set_raw_values($attr, $values) { + $attr = mb_strtolower($attr); + if (is_null($values)) + $values = array(); + else if (!is_array($values)) + $values = array($values); + $this -> changes[$attr] = array(); + foreach($values as $value) + $this -> changes[$attr][] = strval($value); + } + + /** + * Set LDAP attribute values + * @param string $attr + * @param mixed $value + * @return void|false + */ + public function set_value($attr, $value) { + if (!$this -> ldap || !$this -> ldap -> schema) { + $this -> set_raw_values($attr, $value); + return; + } + $attribute = $this -> ldap -> schema -> attribute($attr); + if (!$attribute) + return $this -> error('Unkown attribute %s in schema', null, $attr); + $values = $attribute -> php2ldap($value); + $this -> set_raw_values($attr, $values); + } + /** * Log and eventually raise an error * @param string $error The error message diff --git a/src/Ldap.php b/src/Ldap.php index f6e773c..bab3436 100644 --- a/src/Ldap.php +++ b/src/Ldap.php @@ -21,6 +21,7 @@ namespace EesyLDAP; * @property-read string $scope * @property-read bool $raise_on_error * @property-read bool $use_schema + * @property-read Schema|false $schema */ class Ldap { @@ -391,12 +392,11 @@ class Ldap { case 'default_config': return self :: $default_config; case 'schema': - if ($this->schema) - return $this->schema; if (!$this->use_schema) return false; - $this->schema = Schema :: load($this); - return $this->schema; + if (!$this->schema) + $this->schema = Schema :: load($this); + return $this->schema && $this->schema->loaded?$this->schema:false; } return $this -> error("Invalid property '$key' requested"); } @@ -506,7 +506,7 @@ class Ldap { $attrs[$attr][$k] = $result[$i][$attr][$k]; } // @phpstan-ignore-next-line - $entries[$dn] = new Entry($dn, $attrs); + $entries[$dn] = new Entry($dn, $attrs, $this); } // @phpstan-ignore-next-line return $entries; diff --git a/src/Schema.php b/src/Schema.php index f5eefb4..c156e46 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -2,6 +2,9 @@ namespace EesyLDAP; +/** + * @property-read bool $loaded + */ class Schema { /** @@ -53,6 +56,12 @@ class Schema { */ protected $oids = array(); + /** + * Loaded telltale + * @var bool + */ + protected $loaded = false; + /** * Constructor * @param Ldap $ldap The LDAP connection @@ -63,6 +72,23 @@ class Schema { $this -> ldap = $ldap; $this -> entry = $entry; $this -> parse(); + $this -> loaded = true; + } + + /** + * Magic method to get schema key + * @param string $key + * @return mixed + * @throws \EesyLDAP\InvalidPropertyException + */ + public function __get($key) { + switch ($key) { + case 'loaded': + return $this -> loaded; + } + throw new \EesyLDAP\InvalidPropertyException( + "Invalid property '$key' requested on '".get_called_class()."'" + ); } /** @@ -88,7 +114,7 @@ class Schema { protected function parse() { foreach (self :: $attributes_to_entry_types as $attr => $type) { $type_name = strtolower($type); - foreach($this -> entry -> get_values($attr, array(), true) as $value) { + foreach($this -> entry -> get_raw_values($attr, array(), true) as $value) { // @phpstan-ignore-next-line $entry = call_user_func("\\EesyLDAP\\Schema\\$type::parse", $value); if (!$entry instanceof Schema\SchemaEntry) { diff --git a/src/Schema/Attribute.php b/src/Schema/Attribute.php index f3a24b2..563de7c 100644 --- a/src/Schema/Attribute.php +++ b/src/Schema/Attribute.php @@ -2,6 +2,7 @@ namespace EesyLDAP\Schema; +use EesyLDAP\Schema; /** * @property-read string $oid @@ -69,6 +70,32 @@ class Attribute extends SchemaEntry { 'multiple', ); + /** + * Map attribute syntax OID to corresponding sub Attribute types + * @var array> + */ + protected static $map_syntax_to_subtypes = array( + Schema::SYNTAX_BOOLEAN => Attribute\BooleanAttribute::class, + ); + + /** + * Parse a SubSchema attribute value into a SchemaEntry object + * @param string $value Attribute value + * @return Attribute + */ + public static function parse($value) { + $schema_entry = self :: _parse($value); + if ( + isset($schema_entry['syntax']) + && is_string($schema_entry['syntax']) + && array_key_exists($schema_entry['syntax'], self :: $map_syntax_to_subtypes) + ) + $type = self :: $map_syntax_to_subtypes[$schema_entry['syntax']]; + else + $type = self :: class; + return new $type($schema_entry); + } + /** * Magic method to get attribute schema entry key * @param string $key @@ -82,4 +109,31 @@ class Attribute extends SchemaEntry { } return parent::__get($key); } + + /** + * Convert LDAP value to PHP value + * @param array $value + * @return string|array|null + */ + public function ldap2php($value) { + if ($value) + return $this->single?$value[0]:$value; + return $this->single?null:array(); + } + + /** + * Convert LDAP value to PHP value + * @param mixed $value + * @return array + */ + public function php2ldap($value) { + if (is_null($value)) + return array(); + if (!is_array($value)) + $value = array($value); + $ldap_value = array(); + foreach($value as $v) + $ldap_value[] = strval($v); + return $ldap_value; + } } diff --git a/src/Schema/Attribute/BooleanAttribute.php b/src/Schema/Attribute/BooleanAttribute.php new file mode 100644 index 0000000..9b9f3fc --- /dev/null +++ b/src/Schema/Attribute/BooleanAttribute.php @@ -0,0 +1,68 @@ + $value + * @return bool|null|array + */ + public function ldap2php($value) { + $value = parent::ldap2php($value); + if (is_string($value)) + return self :: _ldap2php($value); + if (is_array($value)) { + $php_value = array(); + foreach($value as $v) + $php_value[] = self :: _ldap2php($v); + return $php_value; + } + return $this->single?null:array(); + } + + /** + * Convert LDAP value to PHP value + * @param mixed $value + * @return array + */ + public function php2ldap($value) { + if (is_null($value)) + return array(); + $value = is_array($value)?$value:array($value); + $ldap_value = array(); + foreach(parent::php2ldap($value) as $value) + $ldap_value[] = $value?self :: TRUE_VALUE:self :: FALSE_VALUE; + return $ldap_value; + } +} diff --git a/src/Schema/SchemaEntry.php b/src/Schema/SchemaEntry.php index d483b87..d72e009 100644 --- a/src/Schema/SchemaEntry.php +++ b/src/Schema/SchemaEntry.php @@ -50,17 +50,15 @@ class SchemaEntry { } /** - * Parses an SubSchema attribute value into a SchemaEntry object + * Parse an SubSchema attribute value into a hash of entry properties * * Note: mainly from PEAR Net_LDAP2_Schema::_parse_entry() * @link https://pear.php.net/package/Net_LDAP2 * * @param string $value Attribute value - * - * @access protected - * @return SchemaEntry + * @return array */ - public static function parse($value) { + public static function _parse($value) { // tokens that have no value associated $noValue = array( 'single-value', @@ -116,6 +114,16 @@ class SchemaEntry { $schema_entry['max_length'] = intval($matches[1]); } } + return $schema_entry; + } + + /** + * Parse a SubSchema attribute value into a SchemaEntry object + * @param string $value Attribute value + * @return SchemaEntry|false + */ + public static function parse($value) { + $schema_entry = self :: _parse($value); $type = get_called_class(); return new $type($schema_entry); }