blob: 2ce83fd2448a1ea135a9fbf9d3fe66d0587ca181 [file] [log] [blame]
<?php
namespace Adldap\Connections;
/**
* Class Ldap.
*
* A class that abstracts PHP's LDAP functions and stores the bound connection.
*/
class Ldap implements ConnectionInterface
{
/**
* The connection name.
*
* @var string|null
*/
protected $name;
/**
* The LDAP host that is currently connected.
*
* @var string|null
*/
protected $host;
/**
* The active LDAP connection.
*
* @var resource
*/
protected $connection;
/**
* The bound status of the connection.
*
* @var bool
*/
protected $bound = false;
/**
* Whether the connection must be bound over SSL.
*
* @var bool
*/
protected $useSSL = false;
/**
* Whether the connection must be bound over TLS.
*
* @var bool
*/
protected $useTLS = false;
/**
* {@inheritdoc}
*/
public function __construct($name = null)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function isUsingSSL()
{
return $this->useSSL;
}
/**
* {@inheritdoc}
*/
public function isUsingTLS()
{
return $this->useTLS;
}
/**
* {@inheritdoc}
*/
public function isBound()
{
return $this->bound;
}
/**
* {@inheritdoc}
*/
public function canChangePasswords()
{
return $this->isUsingSSL() || $this->isUsingTLS();
}
/**
* {@inheritdoc}
*/
public function ssl($enabled = true)
{
$this->useSSL = $enabled;
return $this;
}
/**
* {@inheritdoc}
*/
public function tls($enabled = true)
{
$this->useTLS = $enabled;
return $this;
}
/**
* {@inheritdoc}
*/
public function getHost()
{
return $this->host;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->connection;
}
/**
* {@inheritdoc}
*/
public function getEntries($searchResults)
{
return ldap_get_entries($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function getFirstEntry($searchResults)
{
return ldap_first_entry($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function getNextEntry($entry)
{
return ldap_next_entry($this->connection, $entry);
}
/**
* {@inheritdoc}
*/
public function getAttributes($entry)
{
return ldap_get_attributes($this->connection, $entry);
}
/**
* {@inheritdoc}
*/
public function countEntries($searchResults)
{
return ldap_count_entries($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function compare($dn, $attribute, $value)
{
return ldap_compare($this->connection, $dn, $attribute, $value);
}
/**
* {@inheritdoc}
*/
public function getLastError()
{
return ldap_error($this->connection);
}
/**
* {@inheritdoc}
*/
public function getDetailedError()
{
// If the returned error number is zero, the last LDAP operation
// succeeded. We won't return a detailed error.
if ($number = $this->errNo()) {
ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $message);
return new DetailedError($number, $this->err2Str($number), $message);
}
}
/**
* {@inheritdoc}
*/
public function getValuesLen($entry, $attribute)
{
return ldap_get_values_len($this->connection, $entry, $attribute);
}
/**
* {@inheritdoc}
*/
public function setOption($option, $value)
{
return ldap_set_option($this->connection, $option, $value);
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options = [])
{
foreach ($options as $option => $value) {
$this->setOption($option, $value);
}
}
/**
* {@inheritdoc}
*/
public function setRebindCallback(callable $callback)
{
return ldap_set_rebind_proc($this->connection, $callback);
}
/**
* {@inheritdoc}
*/
public function startTLS()
{
try {
return ldap_start_tls($this->connection);
} catch (\ErrorException $e) {
throw new ConnectionException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function connect($hosts = [], $port = 389)
{
$this->host = $this->getConnectionString($hosts, $this->getProtocol(), $port);
// Reset the bound status if reinitializing the connection.
$this->bound = false;
return $this->connection = ldap_connect($this->host);
}
/**
* {@inheritdoc}
*/
public function close()
{
$connection = $this->connection;
$result = is_resource($connection) ? ldap_close($connection) : false;
$this->bound = false;
return $result;
}
/**
* {@inheritdoc}
*/
public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* {@inheritdoc}
*/
public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* {@inheritdoc}
*/
public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* Extract information from an LDAP result.
*
* @link https://www.php.net/manual/en/function.ldap-parse-result.php
*
* @param resource $result
* @param int $errorCode
* @param string $dn
* @param string $errorMessage
* @param array $referrals
* @param array $serverControls
*
* @return bool
*/
public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = [])
{
return $this->supportsServerControlsInMethods() && !empty($serverControls) ?
ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $serverControls) :
ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals);
}
/**
* {@inheritdoc}
*/
public function bind($username, $password, $sasl = false)
{
// Prior to binding, we will upgrade our connectivity to TLS on our current
// connection and ensure we are not already bound before upgrading.
// This is to prevent subsequent upgrading on several binds.
if ($this->isUsingTLS() && !$this->isBound()) {
$this->startTLS();
}
if ($sasl) {
return $this->bound = ldap_sasl_bind($this->connection, null, null, 'GSSAPI');
}
return $this->bound = ldap_bind(
$this->connection,
$username,
html_entity_decode($password)
);
}
/**
* {@inheritdoc}
*/
public function add($dn, array $entry)
{
return ldap_add($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function delete($dn)
{
return ldap_delete($this->connection, $dn);
}
/**
* {@inheritdoc}
*/
public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
{
return ldap_rename($this->connection, $dn, $newRdn, $newParent, $deleteOldRdn);
}
/**
* {@inheritdoc}
*/
public function modify($dn, array $entry)
{
return ldap_modify($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modifyBatch($dn, array $values)
{
return ldap_modify_batch($this->connection, $dn, $values);
}
/**
* {@inheritdoc}
*/
public function modAdd($dn, array $entry)
{
return ldap_mod_add($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modReplace($dn, array $entry)
{
return ldap_mod_replace($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modDelete($dn, array $entry)
{
return ldap_mod_del($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '')
{
return ldap_control_paged_result($this->connection, $pageSize, $isCritical, $cookie);
}
/**
* {@inheritdoc}
*/
public function controlPagedResultResponse($result, &$cookie)
{
return ldap_control_paged_result_response($this->connection, $result, $cookie);
}
/**
* {@inheritdoc}
*/
public function freeResult($result)
{
return ldap_free_result($result);
}
/**
* {@inheritdoc}
*/
public function errNo()
{
return ldap_errno($this->connection);
}
/**
* {@inheritdoc}
*/
public function getExtendedError()
{
return $this->getDiagnosticMessage();
}
/**
* {@inheritdoc}
*/
public function getExtendedErrorHex()
{
if (preg_match("/(?<=data\s).*?(?=\,)/", $this->getExtendedError(), $code)) {
return $code[0];
}
}
/**
* {@inheritdoc}
*/
public function getExtendedErrorCode()
{
return $this->extractDiagnosticCode($this->getExtendedError());
}
/**
* {@inheritdoc}
*/
public function err2Str($number)
{
return ldap_err2str($number);
}
/**
* {@inheritdoc}
*/
public function getDiagnosticMessage()
{
ldap_get_option($this->connection, LDAP_OPT_ERROR_STRING, $message);
return $message;
}
/**
* {@inheritdoc}
*/
public function extractDiagnosticCode($message)
{
preg_match('/^([\da-fA-F]+):/', $message, $matches);
return isset($matches[1]) ? $matches[1] : false;
}
/**
* Returns the LDAP protocol to utilize for the current connection.
*
* @return string
*/
public function getProtocol()
{
return $this->isUsingSSL() ? $this::PROTOCOL_SSL : $this::PROTOCOL;
}
/**
* Determine if the current PHP version supports server controls.
*
* @return bool
*/
public function supportsServerControlsInMethods()
{
return version_compare(PHP_VERSION, '7.3.0') >= 0;
}
/**
* Generates an LDAP connection string for each host given.
*
* @param string|array $hosts
* @param string $protocol
* @param string $port
*
* @return string
*/
protected function getConnectionString($hosts, $protocol, $port)
{
// If we are using SSL and using the default port, we
// will override it to use the default SSL port.
if ($this->isUsingSSL() && $port == 389) {
$port = self::PORT_SSL;
}
// Normalize hosts into an array.
$hosts = is_array($hosts) ? $hosts : [$hosts];
$hosts = array_map(function ($host) use ($protocol, $port) {
return "{$protocol}{$host}:{$port}";
}, $hosts);
return implode(' ', $hosts);
}
}