Compare commits
2 commits
6b61ecd6f2
...
9520502ece
Author | SHA1 | Date | |
---|---|---|---|
9520502ece | |||
cd303a3a12 |
7 changed files with 698 additions and 34 deletions
|
@ -4,6 +4,8 @@ parameters:
|
|||
- src
|
||||
- tests
|
||||
treatPhpDocTypesAsCertain: false
|
||||
universalObjectCratesClasses:
|
||||
- EesyLDAP\Entry
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#Method .*::test.*\\(\\) has no return type specified\\.#"
|
||||
|
|
144
src/Entry.php
Normal file
144
src/Entry.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?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
|
||||
* @var array<string,array<string>>
|
||||
*/
|
||||
protected $changes = array();
|
||||
|
||||
/**
|
||||
* 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:
|
||||
return $this -> get_values($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entry key is set
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key) {
|
||||
return (
|
||||
in_array($key, array('dn', 'changes'))
|
||||
|| array_key_exists($key, $this -> data)
|
||||
|| array_key_exists($key, $this -> changes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an 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)
|
||||
* @param bool $all_values If True, return all the attribute values, otherwise return only the
|
||||
* first one (optional, default: true)
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_values($attr, $default=null, $all_values=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];
|
||||
if (!$values)
|
||||
return is_null($default) && $all_values?array():$default;
|
||||
return $all_values?$values:$values[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// @phpstan-ignore-next-line
|
||||
error_log($error);
|
||||
throw new LdapException($error); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
|
@ -146,6 +146,9 @@ class Filter {
|
|||
/**
|
||||
* Constructor
|
||||
* @param array<string|Filter|array<string|Filter>|bool> $args
|
||||
* @throws CombineException
|
||||
* @throws FilterException
|
||||
* @throws ParserException
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(...$args) {
|
||||
|
@ -181,7 +184,7 @@ class Filter {
|
|||
|
||||
// Check number of filters against logical operator
|
||||
if (self :: is_not_op($this -> log_op) && count($this -> sub_filters) != 1)
|
||||
throw new FilterException(
|
||||
throw new CombineException(
|
||||
'Invalid constructor arguments: NOT operator must be followed by exactly one filter');
|
||||
return;
|
||||
}
|
||||
|
@ -361,40 +364,17 @@ class Filter {
|
|||
*
|
||||
* @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical
|
||||
* equivalents "&", "|", "!"
|
||||
* @param array<string|Filter|array<string|Filter>> $args LDAP filters to combine: could be
|
||||
* LDAP objects, LDAP strings or array of
|
||||
* LDAP objects or LDAP strings.
|
||||
* @param array<string|Filter|array<string|Filter>> $filters LDAP filters to combine: could be
|
||||
* LDAP objects, LDAP strings or array
|
||||
* of LDAP objects or LDAP strings.
|
||||
* @throws CombineException
|
||||
* @throws FilterException
|
||||
* @throws ParserException
|
||||
* @return Filter
|
||||
* @static
|
||||
*/
|
||||
public static function combine($log_op, ...$args) {
|
||||
// Unalias & check logical operator
|
||||
$log_op = self :: unalias_log_op($log_op);
|
||||
if (!$log_op) throw new CombineException('Invalid logical operator provided!');
|
||||
|
||||
// Convert args as filters
|
||||
$filters = array();
|
||||
foreach ($args as $arg) {
|
||||
if (!is_array($arg))
|
||||
$arg = array($arg);
|
||||
foreach ($arg as $a) {
|
||||
if (is_string($a))
|
||||
$a = self :: parse($a);
|
||||
if (!$a instanceof Filter)
|
||||
throw new CombineException(
|
||||
'Invalid filter provided: must be a Filter object or a LDAP filter string!'
|
||||
);
|
||||
$filters[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
// Check number of filters against logical operator
|
||||
if (self :: is_not_op($log_op)) {
|
||||
if (count($filters) != 1)
|
||||
throw new CombineException('NOT operator accept only one filter!');
|
||||
}
|
||||
|
||||
public static function combine($log_op, ...$filters) {
|
||||
// @phpstan-ignore-next-line
|
||||
return new Filter($log_op, ...$filters);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
namespace EesyLDAP\Filter;
|
||||
|
||||
class FilterException extends \Exception {}
|
||||
class FilterException extends \EesyLDAP\LdapException {}
|
||||
|
|
533
src/Ldap.php
Normal file
533
src/Ldap.php
Normal file
|
@ -0,0 +1,533 @@
|
|||
<?php
|
||||
namespace EesyLDAP;
|
||||
|
||||
/**
|
||||
* @property-read array<string> $hosts
|
||||
* @property-read array<string> $host
|
||||
* @property-read int $port
|
||||
* @property-read int $version
|
||||
* @property-read bool $starttls
|
||||
* @property-read string|null $bind_dn
|
||||
* @property-read string|null $binddn
|
||||
* @property-read string|null $dn
|
||||
* @property-read string|null $bind_password
|
||||
* @property-read string|null $bindpw
|
||||
* @property-read string|null $password
|
||||
* @property-read string|null $pwd
|
||||
* @property-read string|null $basedn
|
||||
* @property-read array $options
|
||||
* @property-read array $required_options
|
||||
* @property-read string $filter
|
||||
* @property-read string $scope
|
||||
* @property-read bool $raise_on_error
|
||||
*/
|
||||
class Ldap {
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
protected $config = array();
|
||||
|
||||
/**
|
||||
* Default configuration
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
protected static $default_config = array(
|
||||
// LDAP server name to connect to. You can provide several hosts in an array in which case the
|
||||
// hosts are tried from left to right.
|
||||
'hosts' => array('localhost'),
|
||||
// Port on the server
|
||||
'port' => 389,
|
||||
// LDAP protocol version
|
||||
'version' => 3,
|
||||
// TLS is started after connecting
|
||||
'starttls' => false,
|
||||
// The distinguished name to bind as (username). If you don't supply this, an anonymous bind
|
||||
// will be established.
|
||||
'bind_dn' => null,
|
||||
// Password for the binddn. If the credentials are wrong, the bind will fail server-side and an
|
||||
// anonymous bind will be established instead. An empty bindpw string requests an
|
||||
// unauthenticated bind. This can cause security problems in your application, if you rely on a
|
||||
// bind to make security decisions (see RFC-4513, section 6.3.1).
|
||||
'bind_password' => null,
|
||||
// LDAP base name (root directory)
|
||||
'basedn' => null,
|
||||
// Array of additional ldap options as key-value pairs
|
||||
'options' => array(),
|
||||
// Required options: consider LDAP link as bad if the required option could not be set.
|
||||
// Possibles values:
|
||||
// - array of expected options
|
||||
// - True to consider all options as required (default)
|
||||
// - False to consider all options as optional
|
||||
'required_options' => true,
|
||||
// Default search filter (string or preferably \EesyLDAP\Filter object).
|
||||
'filter' => '(objectClass=*)',
|
||||
// Default search scope
|
||||
'scope' => 'sub',
|
||||
// Raise LdapException on error
|
||||
'raise_on_error' => true,
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* Configuration parameter aliases
|
||||
* @var array<string,string>
|
||||
*/
|
||||
protected static $config_aliases = array(
|
||||
'host' => 'hosts',
|
||||
'binddn' => 'bind_dn',
|
||||
'dn' => 'bind_dn',
|
||||
'bindpw' => 'bind_password',
|
||||
'password' => 'bind_password',
|
||||
'pwd' => 'bind_password',
|
||||
);
|
||||
|
||||
/**
|
||||
* The LDAP connection
|
||||
* @var resource|false
|
||||
*/
|
||||
protected $_link = false;
|
||||
|
||||
/**
|
||||
* Scopes associated with their search function
|
||||
* @var array<string,callable>
|
||||
*/
|
||||
protected static $search_function_by_scope = array(
|
||||
'one' => 'ldap_list',
|
||||
'base' => 'ldap_read',
|
||||
'sub' => 'ldap_search',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param array<string,mixed>|null $config Configuration (see $default_config for expected content)
|
||||
* @param bool $connect If true, start LDAP connection (optional, default=true)
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($config=null, $connect=true) {
|
||||
if (is_array($config))
|
||||
$this -> set_config($config);
|
||||
if ($connect)
|
||||
$this -> connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration
|
||||
* @param array<string,mixed> $config
|
||||
* @return bool
|
||||
*/
|
||||
public function set_config($config) {
|
||||
if (!is_array($config))
|
||||
return $this->error('Expect $config to be an array');
|
||||
|
||||
foreach($config as $key => $value) {
|
||||
if (array_key_exists($key, self :: $config_aliases))
|
||||
$key = self :: $config_aliases[$key];
|
||||
if (!array_key_exists($key, self :: $default_config))
|
||||
return $this -> error("Invalid configuration parameter '$key'");
|
||||
switch ($key) {
|
||||
case 'hosts':
|
||||
if (is_string($value)) {
|
||||
$hosts = array();
|
||||
foreach(explode(',', $value) as $host) {
|
||||
$host = trim($host);
|
||||
if (!$host)
|
||||
return $this -> error("Empty host found in configuration!");
|
||||
$hosts[] = $host;
|
||||
}
|
||||
$value = $hosts;
|
||||
}
|
||||
else if (!is_array($value)) {
|
||||
$this -> error(
|
||||
"Invalid hosts found in configuration: expect to be an array of LDAP host URI (or ".
|
||||
"hostname) or a comma separated list of LDAP host URI (or hostname)."
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case 'port':
|
||||
case 'version':
|
||||
$value = intval($value);
|
||||
break;
|
||||
case 'starttls':
|
||||
$value = boolval($value);
|
||||
break;
|
||||
case 'options':
|
||||
if (!is_array($value)) {
|
||||
$this -> error(
|
||||
"Invalid options found in configuration: expect to be an array."
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case 'required_options':
|
||||
if (!is_array($value) && !is_bool($value)) {
|
||||
$this -> error(
|
||||
"Invalid required_options found in configuration: expect to be an array or a boolean."
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case 'filter':
|
||||
if (is_string($value))
|
||||
$value = new Filter($value);
|
||||
elseif (!$value instanceof Filter) {
|
||||
$this -> error(
|
||||
"Invalid filter found in configuration: expect to be a string or a \EesyLDAP\Filter ".
|
||||
"object."
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case 'scope':
|
||||
if (!is_string($value) || !array_key_exists($value, self :: $search_function_by_scope)){
|
||||
$this -> error(
|
||||
"Invalid scope '%s' found in configuration: expect to be one of the following: %s",
|
||||
null,
|
||||
is_string($value)?$value:gettype($value),
|
||||
implode(', ', array_keys(self :: $search_function_by_scope))
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
$this -> config[$key] = $value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if phps ldap-extension is loaded
|
||||
*
|
||||
* Note: If it is not loaded, it tries to load it manually using PHPs dl().
|
||||
* It knows both windows-dll and *nix-so.
|
||||
*
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
* @return bool
|
||||
*/
|
||||
public function check_ldap_extension($raise=null) {
|
||||
if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
|
||||
return $this -> error(
|
||||
"It seems that you do not have the ldap-extension installed. Please install it before ".
|
||||
"using the EesyLDAP.",
|
||||
$raise
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start connection on configured LDAP hosts
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
* @param string|null $bind_dn Bind DN (optional, default: null = use configuration)
|
||||
* @param string|null $bind_password Bind password (optional, default: null = use configuration)
|
||||
* @return bool
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function connect($raise=null, $bind_dn=null, $bind_password=null) {
|
||||
if ($this -> _link)
|
||||
return true;
|
||||
if (!self :: check_ldap_extension($raise))
|
||||
return false;
|
||||
|
||||
foreach($this -> hosts as $host) {
|
||||
$this->_link = @ldap_connect($host, $this->port);
|
||||
if ($this->_link === false) {
|
||||
$this -> error("Fail to connect on $host", false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->starttls) {
|
||||
if (@ldap_start_tls($this->_link) === false) {
|
||||
$this -> close();
|
||||
$this -> error("Fail to start TLS on $host", false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->version && !$this->set_option("LDAP_OPT_PROTOCOL_VERSION", $this->version, false)) {
|
||||
$this -> close();
|
||||
$this -> error(
|
||||
"Fail to switch to LDAP protocol version %d on %s", false, $this->version, $host);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this -> bind($bind_dn, $bind_password)) {
|
||||
$this -> close();
|
||||
$this -> error(
|
||||
"Fail to bind on %s", false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set LDAP parameters, now we know we have a valid connection.
|
||||
foreach ($this->options as $option => $value) {
|
||||
if (!$this -> set_option($option, $value, false)) {
|
||||
if (in_array($option, $this -> required_options)) {
|
||||
$this -> close();
|
||||
$this -> error('Fail to set option %s on %s', false, $option, $host);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this -> error('Fail to connect on configured host(s)', $raise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind on LDAP link
|
||||
* Note: if not already connected, start connection with the specified credentials.
|
||||
* @param string|null $dn Bind DN (optional, default: null = use configuration)
|
||||
* @param string|null $password Bind password (optional, default: null = use configuration)
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
* @return bool
|
||||
*/
|
||||
public function bind($dn=null, $password=null, $raise=null) {
|
||||
if (is_null($dn)) $dn = $this -> bind_dn;
|
||||
if (is_null($password)) $password = $this -> bind_password;
|
||||
if (!$this -> _link) return $this -> connect($raise, $dn, $password);
|
||||
if (@ldap_bind($this->_link, $dn, $password) === false)
|
||||
return $this -> log_error($dn?"Fail to bind as $dn":"Fail to anonymously bind", $raise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an LDAP option
|
||||
*
|
||||
* @param string $option Option to set
|
||||
* @param mixed $value Value to set Option to
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function set_option($option, $value, $raise=null) {
|
||||
if (!$this->_link)
|
||||
return $this->error("Could not set LDAP option: No LDAP connection", $raise);
|
||||
if (!is_string($option) || !defined($option) || strpos($option, 'LDAP_') !== 0)
|
||||
return $this->error("Unkown Option requested", $raise);
|
||||
// @phpstan-ignore-next-line
|
||||
if (@ldap_set_option($this->_link, constant($option), $value))
|
||||
return true;
|
||||
return $this -> log_error("Fail to set option $option", $raise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close LDAP link (if established)
|
||||
* @return void
|
||||
*/
|
||||
public function close() {
|
||||
if ($this->_link)
|
||||
@ldap_close($this->_link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the last error occured on LDAP link
|
||||
* @param string $prefix
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
* @param array<mixed> $extra_args If passed, will be used to compute the prefix message using sprintf
|
||||
* @return false
|
||||
*/
|
||||
protected function log_error($prefix, $raise=null, ...$extra_args) {
|
||||
if ($extra_args)
|
||||
$prefix = call_user_func_array('sprintf', array_merge(array($prefix), $extra_args));
|
||||
$errno = $this->_link?@ldap_errno($this->_link):false;
|
||||
if (!$errno)
|
||||
return $this -> error("%s: unkown error", $raise, $prefix);
|
||||
$err = @ldap_err2str($errno);
|
||||
if (!$err)
|
||||
return $this -> error("%s: error #%s", $raise, $errno);
|
||||
return $this -> error("%s: %s (#%s)", $raise, $err, $errno);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log and eventually raise an error
|
||||
* @param string $error The error message
|
||||
* @param bool|null $raise If true, raise an LdapException, otherwise only log it and return false
|
||||
* @param array<mixed> $extra_args If passed, will be used to compute the error message using sprintf
|
||||
* @return false
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function error($error, $raise=null, ...$extra_args) {
|
||||
if ($extra_args)
|
||||
$error = call_user_func_array('sprintf', array_merge(array($error), $extra_args));
|
||||
// Note: sprintf always return string
|
||||
// @phpstan-ignore-next-line
|
||||
error_log($error);
|
||||
if (is_null($raise))
|
||||
$raise = $this -> raise_on_error;
|
||||
if ($raise)
|
||||
throw new LdapException($error); // @phpstan-ignore-line
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function __get($key) {
|
||||
if (
|
||||
array_key_exists($key, self :: $default_config)
|
||||
|| array_key_exists($key, self :: $config_aliases)
|
||||
)
|
||||
return $this -> get_config($key);
|
||||
switch ($key) {
|
||||
case 'config_aliases':
|
||||
return self :: $config_aliases;
|
||||
case 'default_config':
|
||||
return self :: $default_config;
|
||||
}
|
||||
return $this -> error("Invalid property '$key' requested");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration parameter
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function get_config($key) {
|
||||
if (array_key_exists($key, self :: $config_aliases))
|
||||
$key = self :: $config_aliases[$key];
|
||||
|
||||
$value = null;
|
||||
if (array_key_exists($key, $this -> config))
|
||||
$value = $this -> config[$key];
|
||||
elseif (array_key_exists($key, self :: $default_config))
|
||||
$value = self :: $default_config[$key];
|
||||
else
|
||||
return $this -> error("Invalid configuration parameter '$key'");
|
||||
switch ($key) {
|
||||
case 'required_options':
|
||||
if (is_array($value))
|
||||
return $value;
|
||||
if ($value === false)
|
||||
return array();
|
||||
return array_keys($this->options);
|
||||
case 'filter':
|
||||
if (is_string($value))
|
||||
return Filter :: parse($value);
|
||||
return $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search on LDAP directory
|
||||
*
|
||||
* Optional expected parameters are:
|
||||
* - scope: The scope which will be used for searching.
|
||||
* Possible values:
|
||||
* - base: Just one entry (the specified base DN, see $base)
|
||||
* - sub: The whole subtree
|
||||
* - one: Immediately below the specified base DN (see $base)
|
||||
* - sizelimit: Limit the number of entries returned (default: 0 = unlimited),
|
||||
* - timelimit: Limit the time spent for searching in seconds (default: 0 = unlimited),
|
||||
* - attrsonly: If true, the search will only return the attribute names,
|
||||
* - attributes: Array of attribute names, which the entries in the expected result should contain.
|
||||
* It is good practice to limit this to just the ones you need.
|
||||
*
|
||||
* @param string|null $base Base DN of the search (optional, default: null == use config)
|
||||
* @param string|null $filter Filter of the search (optional, default: null == use config)
|
||||
* @param array<string,mixed>|null $params Other optional parameters of the search (optional, default: null == use config)
|
||||
* @param bool|null $raise See error() (optional, default: null)
|
||||
* @return array<string|Entry>|false Array of Entry object with DN as key, or False in case of error
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function search($base=null, $filter=null, $params=null, $raise=null) {
|
||||
if (!$this -> _link)
|
||||
return $this -> error("Can't search: no LDAP link");
|
||||
if (is_null($base))
|
||||
$base = $this->basedn;
|
||||
elseif ($base instanceof Entry)
|
||||
$base = $base->dn?$base->dn:null;
|
||||
|
||||
if (is_null($filter)) $filter = $this->filter;
|
||||
if ($filter instanceof Filter)
|
||||
$filter = $filter->as_string(); // convert Filter object as string
|
||||
|
||||
// Adjust search function to expected scope
|
||||
$scope = self :: get_param($params, 'scope', $this->scope, 'string');
|
||||
if (!is_string($scope) || !array_key_exists($scope, self :: $search_function_by_scope))
|
||||
return $this -> error("Invalid scope '%s' specified", $raise, $scope);
|
||||
|
||||
// Run the search
|
||||
$search = @call_user_func(
|
||||
self :: $search_function_by_scope[$scope],
|
||||
$this->_link,
|
||||
$base,
|
||||
$filter,
|
||||
self :: get_param($params, 'attributes', array(), 'array'),
|
||||
self :: get_param($params, 'attrsonly', false, 'bool')?1:0,
|
||||
self :: get_param($params, 'sizelimit', 0, 'int'),
|
||||
self :: get_param($params, 'timelimit', 0, 'int')
|
||||
);
|
||||
|
||||
if (!$search)
|
||||
return $this -> log_error(
|
||||
"Error occured searching on base '%s' with filter '%s'", $raise, $base, $filter);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$result = ldap_get_entries($this->_link, $search);
|
||||
if (!is_array($result))
|
||||
return $this -> log_error(
|
||||
"Error occured retreiving the result of the search on base '%s' with filter '%s'",
|
||||
$raise, $base, $filter
|
||||
);
|
||||
$entries = array();
|
||||
for ($i=0; $i<$result['count']; $i++) {
|
||||
$dn = $result[$i]['dn'];
|
||||
$attrs = array();
|
||||
for($j=0; $j<$result[$i]['count']; $j++) {
|
||||
$attr = $result[$i][$j];
|
||||
$attrs[$attr] = array();
|
||||
for($k=0; $k<$result[$i][$attr]['count']; $k++)
|
||||
$attrs[$attr][$k] = $result[$i][$attr][$k];
|
||||
}
|
||||
// @phpstan-ignore-next-line
|
||||
$entries[$dn] = new Entry($dn, $attrs);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter value
|
||||
* @param array<string,mixed>|null $params Array of provided parameter values
|
||||
* @param string $key Expected parameter key
|
||||
* @param mixed $default Default value if parameter if not set (optional, default: null)
|
||||
* @param string|null $cast Expected type of value: if provided, the parameter value will be casted
|
||||
* as the expected type (optional, default: null == no cast)
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function get_param($params, $key, $default=null, $cast=null) {
|
||||
if (!is_array($params) || !array_key_exists($key, $params))
|
||||
return $default;
|
||||
$value = $params[$key];
|
||||
switch($cast) {
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return boolval($value);
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return intval($value);
|
||||
case 'float':
|
||||
return floatval($value);
|
||||
case 'str':
|
||||
case 'string':
|
||||
return strval($value);
|
||||
case 'array':
|
||||
if (is_array($value))
|
||||
return $value;
|
||||
if (is_null($value))
|
||||
return array();
|
||||
return array($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
5
src/LdapException.php
Normal file
5
src/LdapException.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace EesyLDAP;
|
||||
|
||||
class LdapException extends \Exception {}
|
|
@ -434,7 +434,7 @@ final class FilterTest extends TestCase {
|
|||
* @covers \EesyLDAP\Filter::combine
|
||||
*/
|
||||
public function testCombineInvalidLogOp() {
|
||||
$this->expectException(CombineException::class);
|
||||
$this->expectException(FilterException::class);
|
||||
$a = Filter::combine('X', new Filter('a', '=', 'b'), new Filter('c', '>=', '2'));
|
||||
}
|
||||
|
||||
|
@ -442,7 +442,7 @@ final class FilterTest extends TestCase {
|
|||
* @covers \EesyLDAP\Filter::combine
|
||||
*/
|
||||
public function testCombineInvalidFilter() {
|
||||
$this->expectException(CombineException::class);
|
||||
$this->expectException(FilterException::class);
|
||||
// @phpstan-ignore-next-line
|
||||
$a = Filter::combine('&', new Filter('a', '=', 'b'), new FilterException('test'));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue