Compare commits

..

No commits in common. "191a1196060270243502288e4d06b3585ea95968" and "db27becda58d5454610925d68a13bd295d30791f" have entirely different histories.

7 changed files with 37 additions and 1538 deletions

View file

@ -4,9 +4,7 @@
"description": "Object oriented interface for searching and manipulating LDAP entries & filters", "description": "Object oriented interface for searching and manipulating LDAP entries & filters",
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9"
"lstrojny/phpunit-function-mocker": "^1.1",
"mockery/mockery": "^1.5"
}, },
"authors": [ "authors": [
{ {

View file

@ -10,9 +10,3 @@ parameters:
- -
message: "#Method .*::test.*\\(\\) has no return type specified\\.#" message: "#Method .*::test.*\\(\\) has no return type specified\\.#"
path: tests/* path: tests/*
-
message: "#Call to an undefined method Mockery\\\\ExpectationInterface|Mockery\\\\HigherOrderMessage::once\\(\\)\\.#"
path: tests/*
-
message: "#Call to an undefined method PHPUnit\\\\Extension\\\\FunctionMocker::expects\\(\\)\\.#"
path: tests/*

View file

@ -416,6 +416,8 @@ class Entry {
if ($this->ldap) if ($this->ldap)
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
return $this->ldap->error($error); return $this->ldap->error($error);
// @phpstan-ignore-next-line
error_log($error);
throw new LdapException($error); // @phpstan-ignore-line throw new LdapException($error); // @phpstan-ignore-line
} }

View file

@ -21,8 +21,6 @@ namespace EesyLDAP;
* @property-read string $scope * @property-read string $scope
* @property-read bool $raise_on_error * @property-read bool $raise_on_error
* @property-read bool $use_schema * @property-read bool $use_schema
* @property-read array<string,string> $config_aliases
* @property-read array<string,mixed> $default_config
* @property-read Schema|false $schema * @property-read Schema|false $schema
*/ */
class Ldap { class Ldap {
@ -90,7 +88,7 @@ class Ldap {
/** /**
* The LDAP connection * The LDAP connection
* @var Link|false * @var resource|false
*/ */
protected $_link = false; protected $_link = false;
@ -102,12 +100,12 @@ class Ldap {
/** /**
* Scopes associated with their search function * Scopes associated with their search function
* @var array<string,string> * @var array<string,callable>
*/ */
protected static $search_function_by_scope = array( protected static $search_function_by_scope = array(
'one' => 'list', 'one' => 'ldap_list',
'base' => 'read', 'base' => 'ldap_read',
'sub' => 'search', 'sub' => 'ldap_search',
); );
/** /**
@ -182,7 +180,7 @@ class Ldap {
break; break;
case 'filter': case 'filter':
if (is_string($value)) if (is_string($value))
$value = Filter::parse($value); $value = new Filter($value);
elseif (!$value instanceof Filter) { elseif (!$value instanceof Filter) {
$this -> error( $this -> error(
"Invalid filter found in configuration: expect to be a string or a \EesyLDAP\Filter ". "Invalid filter found in configuration: expect to be a string or a \EesyLDAP\Filter ".
@ -201,6 +199,8 @@ class Ldap {
); );
continue 2; continue 2;
} }
} }
$this -> config[$key] = $value; $this -> config[$key] = $value;
} }
@ -238,19 +238,18 @@ class Ldap {
public function connect($raise=null, $bind_dn=null, $bind_password=null) { public function connect($raise=null, $bind_dn=null, $bind_password=null) {
if ($this -> _link) if ($this -> _link)
return true; return true;
if (!$this -> check_ldap_extension($raise)) if (!self :: check_ldap_extension($raise))
return false; return false;
foreach($this -> hosts as $host) { foreach($this -> hosts as $host) {
$this->_link = new Link(); $this->_link = @ldap_connect($host, $this->port);
if ($this->_link->connect($host, $this->port) === false) { if ($this->_link === false) {
$this->_link = false;
$this -> error("Fail to connect on $host", false); $this -> error("Fail to connect on $host", false);
continue; continue;
} }
if ($this->starttls) { if ($this->starttls) {
if ($this->_link->start_tls() === false) { if (@ldap_start_tls($this->_link) === false) {
$this -> close(); $this -> close();
$this -> error("Fail to start TLS on $host", false); $this -> error("Fail to start TLS on $host", false);
continue; continue;
@ -264,7 +263,7 @@ class Ldap {
continue; continue;
} }
if (!$this -> bind($bind_dn, $bind_password, false)) { if (!$this -> bind($bind_dn, $bind_password)) {
$this -> close(); $this -> close();
$this -> error( $this -> error(
"Fail to bind on %s", false); "Fail to bind on %s", false);
@ -277,7 +276,7 @@ class Ldap {
if (in_array($option, $this -> required_options)) { if (in_array($option, $this -> required_options)) {
$this -> close(); $this -> close();
$this -> error('Fail to set option %s on %s', false, $option, $host); $this -> error('Fail to set option %s on %s', false, $option, $host);
continue 2; continue;
} }
} }
} }
@ -299,7 +298,7 @@ class Ldap {
if (is_null($dn)) $dn = $this -> bind_dn; if (is_null($dn)) $dn = $this -> bind_dn;
if (is_null($password)) $password = $this -> bind_password; if (is_null($password)) $password = $this -> bind_password;
if (!$this -> _link) return $this -> connect($raise, $dn, $password); if (!$this -> _link) return $this -> connect($raise, $dn, $password);
if ($this->_link->bind($dn, $password) === false) if (@ldap_bind($this->_link, $dn, $password) === false)
return $this -> log_error($dn?"Fail to bind as $dn":"Fail to anonymously bind", $raise); return $this -> log_error($dn?"Fail to bind as $dn":"Fail to anonymously bind", $raise);
return true; return true;
} }
@ -321,7 +320,7 @@ class Ldap {
if (!is_string($option) || !defined($option) || strpos($option, 'LDAP_') !== 0) if (!is_string($option) || !defined($option) || strpos($option, 'LDAP_') !== 0)
return $this->error("Unkown Option requested", $raise); return $this->error("Unkown Option requested", $raise);
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
if ($this->_link->set_option(constant($option), $value)) if (@ldap_set_option($this->_link, constant($option), $value))
return true; return true;
return $this -> log_error("Fail to set option $option", $raise); return $this -> log_error("Fail to set option $option", $raise);
} }
@ -332,7 +331,7 @@ class Ldap {
*/ */
public function close() { public function close() {
if ($this->_link) if ($this->_link)
$this->_link->close(); @ldap_close($this->_link);
} }
/** /**
@ -345,13 +344,13 @@ class Ldap {
protected function log_error($prefix, $raise=null, ...$extra_args) { protected function log_error($prefix, $raise=null, ...$extra_args) {
if ($extra_args) if ($extra_args)
$prefix = call_user_func_array('sprintf', array_merge(array($prefix), $extra_args)); $prefix = call_user_func_array('sprintf', array_merge(array($prefix), $extra_args));
$errno = $this->_link?$this->_link->errno():false; $errno = $this->_link?@ldap_errno($this->_link):false;
if (!$errno) if (!$errno)
return $this -> error("%s: unkown error", $raise, $prefix); return $this -> error("%s: unkown error", $raise, $prefix);
$err = $this->_link?$this->_link->err2str($errno):false; $err = @ldap_err2str($errno);
if (!$err) if (!$err)
return $this -> error("%s: error #%s", $raise, $prefix, $errno); return $this -> error("%s: error #%s", $raise, $errno);
return $this -> error("%s: %s (#%s)", $raise, $prefix, $err, $errno); return $this -> error("%s: %s (#%s)", $raise, $err, $errno);
} }
/** /**
@ -366,6 +365,8 @@ class Ldap {
if ($extra_args) if ($extra_args)
$error = call_user_func_array('sprintf', array_merge(array($error), $extra_args)); $error = call_user_func_array('sprintf', array_merge(array($error), $extra_args));
// Note: sprintf always return string // Note: sprintf always return string
// @phpstan-ignore-next-line
error_log($error);
if (is_null($raise)) if (is_null($raise))
$raise = $this -> raise_on_error; $raise = $this -> raise_on_error;
if ($raise) if ($raise)
@ -447,17 +448,14 @@ class Ldap {
* - attributes: Array of attribute names, which the entries in the expected result should contain. * - 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. * 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 string|null $filter Filter of the search (optional, default: null == use config)
* @param string|Entry|null $base Base DN of the search (optional, default: null == use config)
* @param string|null $scope Scope of the search (optional, default: null == use params or config)
* @param array<string>|null $attributes Expected attributes returned by the search (optional,
* default: null == use params or config)
* @param array<string,mixed>|null $params Other optional parameters 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) * @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 * @return array<string,Entry>|false Array of Entry object with DN as key, or False in case of error
* @throws LdapException * @throws LdapException
*/ */
public function search($filter=null, $base=null, $scope=null, $attributes=null, $params=null, $raise=null) { public function search($base=null, $filter=null, $params=null, $raise=null) {
if (!$this -> _link) if (!$this -> _link)
return $this -> error("Can't search: no LDAP link"); return $this -> error("Can't search: no LDAP link");
if (is_null($base)) if (is_null($base))
@ -470,22 +468,17 @@ class Ldap {
$filter = $filter->as_string(); // convert Filter object as string $filter = $filter->as_string(); // convert Filter object as string
// Adjust search function to expected scope // Adjust search function to expected scope
$scope = $scope?$scope:self :: get_param($params, 'scope', $this->scope, 'string'); $scope = self :: get_param($params, 'scope', $this->scope, 'string');
if (!is_string($scope) || !array_key_exists($scope, self :: $search_function_by_scope)) if (!is_string($scope) || !array_key_exists($scope, self :: $search_function_by_scope))
return $this -> error("Invalid scope '%s' specified", $raise, $scope); return $this -> error("Invalid scope '%s' specified", $raise, $scope);
// Compute expected attributes
$attributes = $attributes?$attributes:self :: get_param($params, 'attributes', array(), 'array');
if (!is_array($attributes))
return $this -> error("Invalid expected attributes specified: must be an array", $raise);
// Run the search // Run the search
$search = @call_user_func( $search = @call_user_func(
// @phpstan-ignore-next-line self :: $search_function_by_scope[$scope],
array($this->_link, self :: $search_function_by_scope[$scope]), $this->_link,
$base, $base,
$filter, $filter,
$attributes, self :: get_param($params, 'attributes', array(), 'array'),
self :: get_param($params, 'attrsonly', false, 'bool')?1:0, self :: get_param($params, 'attrsonly', false, 'bool')?1:0,
self :: get_param($params, 'sizelimit', 0, 'int'), self :: get_param($params, 'sizelimit', 0, 'int'),
self :: get_param($params, 'timelimit', 0, 'int') self :: get_param($params, 'timelimit', 0, 'int')
@ -496,7 +489,7 @@ class Ldap {
"Error occured searching on base '%s' with filter '%s'", $raise, $base, $filter); "Error occured searching on base '%s' with filter '%s'", $raise, $base, $filter);
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
$result = $this->_link->get_entries($search); $result = ldap_get_entries($this->_link, $search);
if (!is_array($result)) if (!is_array($result))
return $this -> log_error( return $this -> log_error(
"Error occured retreiving the result of the search on base '%s' with filter '%s'", "Error occured retreiving the result of the search on base '%s' with filter '%s'",
@ -527,17 +520,16 @@ class Ldap {
* *
* @param string $dn DN of the expected entry * @param string $dn DN of the expected entry
* @param string|null $filter Filter of the search (optional, default: null == use config) * @param string|null $filter Filter of the search (optional, default: null == use config)
* @param array<string>|null $attributes Expected attributes returned by the search (optional,
* default: null == use params or config)
* @param array<string,mixed>|null $params Other optional parameters of the search (optional, * @param array<string,mixed>|null $params Other optional parameters of the search (optional,
* default: null == use config, see search()) * default: null == use config, see search())
* @param bool|null $raise See error() (optional, default: null) * @param bool|null $raise See error() (optional, default: null)
* @return Entry|false The requested Entry object, or False in case of error * @return Entry|false The requested Entry object, or False in case of error
* @throws LdapException * @throws LdapException
*/ */
public function get_entry($dn, $filter=null, $attributes=null, $params=null, $raise=null) { public function get_entry($dn, $filter=null, $params=null, $raise=null) {
$params = is_array($params)?$params:array(); $params = is_array($params)?$params:array();
$entries = $this -> search($filter, $dn, 'base', $attributes, $params, $raise); $params['scope'] = 'base';
$entries = $this -> search($dn, $filter, $params, $raise);
if (is_array($entries) && count($entries) == 1) if (is_array($entries) && count($entries) == 1)
return array_pop($entries); return array_pop($entries);
return false; return false;

View file

@ -1,194 +0,0 @@
<?php
namespace EesyLDAP;
class Link {
/**
* The LDAP connection
* @var resource|false
*/
protected $_link = false;
/**
* Connect on LDAP host
* @param string $uri LDAP server URI
* @param int|null $port Optional LDAP server port (default: null == 389)
* @see ldap_connect()
* @return bool
*/
public function connect($uri, $port=null) {
$this->_link = @ldap_connect($uri, is_null($port)?389:$port);
return boolval($this->_link);
}
/**
* Start TLS
* @see ldap_start_tls()
* @return bool
*/
public function start_tls() {
if (!$this -> _link) return false;
return @ldap_start_tls($this->_link);
}
/**
* Return the LDAP error number of the last LDAP command
* @see ldap_errno()
* @return int|false
*/
public function errno() {
if (!$this -> _link) return false;
return @ldap_errno($this->_link);
}
/**
* Convert LDAP error number into string error message
* @param int $errno
* @see ldap_err2str()
* @return string
*/
public function err2str($errno) {
return @ldap_err2str($errno);
}
/**
* Bind on LDAP link
* @param string|null $dn Bind DN (optional, default: null = anonymous bind)
* @param string|null $password Bind password (optional, default: null = anonymous bind)
* @see ldap_bind()
* @return bool
*/
public function bind($dn=null, $password=null) {
if (!$this -> _link) return false;
return @ldap_bind($this->_link, $dn, $password);
}
/**
* Set an LDAP option
*
* @param int $option Option to set
* @param string|int|bool $value Value to set Option to
*
* @see ldap_set_option()
* @access public
* @return bool
*/
public function set_option($option, $value) {
if (!$this->_link)
return false;
return @ldap_set_option($this->_link, $option, $value);
}
/**
* Single-level search
*
* @param string $base The base DN of the search
* @param string $filter The LDAP filter string of the search
* @param array<string> $attributes Array of expected attribute names (optional, default: all)
* @param int $attributes_only If 1, only attribute names will be return (optional, default: 0)
* @param int $sizelimit Limit the count of entries fetched (optional, default: -1)
* @param int $timelimit Number of seconds how long is spend on the search (optional, default: -1)
* @param int $deref Specifies how aliases should be handled during the search (optional,
* default: LDAP_DEREF_NEVER)
* @param array<array<mixed>> $controls Array of LDAP Controls to send with the request
* (optional, default: null)
*
* @see ldap_list()
* @access public
* @return resource|false
*/
public function list(
$base, $filter, $attributes=[], $attributes_only=0, $sizelimit=-1, $timelimit=-1,
$deref=LDAP_DEREF_NEVER, $controls=[]
) {
if (!$this->_link)
return false;
return @ldap_list(
$this->_link, $base, $filter, $attributes, $attributes_only, $sizelimit, $timelimit, $deref,
$controls
);
}
/**
* Read an LDAP entry
*
* @param string $base The base DN of the search
* @param string $filter The LDAP filter string of the search
* @param array<string> $attributes Array of expected attribute names (optional, default: all)
* @param int $attributes_only If 1, only attribute names will be return (optional, default: 0)
* @param int $sizelimit Limit the count of entries fetched (optional, default: -1)
* @param int $timelimit Number of seconds how long is spend on the search (optional, default: -1)
* @param int $deref Specifies how aliases should be handled during the search (optional,
* default: LDAP_DEREF_NEVER)
* @param array<array<mixed>> $controls Array of LDAP Controls to send with the request
* (optional, default: empty array)
*
* @see ldap_read()
* @access public
* @return resource|false
*/
public function read(
$base, $filter, $attributes=[], $attributes_only=0, $sizelimit=-1, $timelimit=-1,
$deref=LDAP_DEREF_NEVER, $controls=[]
) {
if (!$this->_link)
return false;
return ldap_read(
$this->_link, $base, $filter, $attributes, $attributes_only, $sizelimit, $timelimit, $deref,
$controls
);
}
/**
* Search in LDAP tree
*
* @param string $base The base DN of the search
* @param string $filter The LDAP filter string of the search
* @param array<string> $attributes Array of expected attribute names (optional, default: all)
* @param int $attributes_only If 1, only attribute names will be return (optional, default: 0)
* @param int $sizelimit Limit the count of entries fetched (optional, default: -1)
* @param int $timelimit Number of seconds how long is spend on the search (optional, default: -1)
* @param int $deref Specifies how aliases should be handled during the search (optional,
* default: LDAP_DEREF_NEVER)
* @param array<array<mixed>> $controls Array of LDAP Controls to send with the request
* (optional, default: empty array)
*
* @see ldap_search()
* @access public
* @return resource|false
*/
public function search(
$base, $filter, $attributes=[], $attributes_only=0, $sizelimit=-1, $timelimit=-1,
$deref=LDAP_DEREF_NEVER, $controls=[]
) {
if (!$this->_link)
return false;
return @ldap_search(
$this->_link, $base, $filter, $attributes, $attributes_only, $sizelimit, $timelimit, $deref,
$controls
);
}
/**
* Get all result entries
* @param resource $result An LDAP\Result instance, returned by ldap_list() or ldap_search()
* @see ldap_get_entries()
* @return array<mixed>|false
*/
public function get_entries($result) {
if (!$this -> _link) return false;
return @ldap_get_entries($this->_link, $result);
}
/**
* Close LDAP link (if established)
* @see ldap_close()
* @return void
*/
public function close() {
if ($this->_link)
@ldap_close($this->_link);
}
}

View file

@ -100,7 +100,7 @@ class Schema {
public static function load(&$ldap, $raise=null) { public static function load(&$ldap, $raise=null) {
$entry = $ldap->get_entry( $entry = $ldap->get_entry(
'cn=SubSchema', '(objectclass=*)', 'cn=SubSchema', '(objectclass=*)',
array_keys(self :: $attributes_to_entry_types) array('attributes' => array_keys(self :: $attributes_to_entry_types))
); );
if (!$entry instanceof Entry) if (!$entry instanceof Entry)
return $ldap->error('Fail to load cn=SubSchema entry', $raise); return $ldap->error('Fail to load cn=SubSchema entry', $raise);

File diff suppressed because it is too large Load diff