Move capability-related code of the ldap connection into a separate class
Achieve a better separation between the different concerns, more readable code and get rid of unused dead code.
This commit is contained in:
parent
7bb78330a9
commit
e93a5f16d9
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
/**
|
||||
* The properties and capabilities of an LDAP server
|
||||
*
|
||||
* Provides information about the available encryption mechanisms (StartTLS), the supported
|
||||
* LDAP protocol (v2/v3), vendor-specific extensions or protocols controls and extensions.
|
||||
*/
|
||||
class Capability {
|
||||
|
||||
const LDAP_SERVER_START_TLS_OID = '1.3.6.1.4.1.1466.20037';
|
||||
|
||||
const LDAP_PAGED_RESULT_OID_STRING = '1.2.840.113556.1.4.319';
|
||||
|
||||
const LDAP_SERVER_SHOW_DELETED_OID = '1.2.840.113556.1.4.417';
|
||||
|
||||
const LDAP_SERVER_SORT_OID = '1.2.840.113556.1.4.473';
|
||||
|
||||
const LDAP_SERVER_CROSSDOM_MOVE_TARGET_OID = '1.2.840.113556.1.4.521';
|
||||
|
||||
const LDAP_SERVER_NOTIFICATION_OID = '1.2.840.113556.1.4.528';
|
||||
|
||||
const LDAP_SERVER_EXTENDED_DN_OID = '1.2.840.113556.1.4.529';
|
||||
|
||||
const LDAP_SERVER_LAZY_COMMIT_OID = '1.2.840.113556.1.4.619';
|
||||
|
||||
const LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801';
|
||||
|
||||
const LDAP_SERVER_TREE_DELETE_OID = '1.2.840.113556.1.4.805';
|
||||
|
||||
const LDAP_SERVER_DIRSYNC_OID = '1.2.840.113556.1.4.841';
|
||||
|
||||
const LDAP_SERVER_VERIFY_NAME_OID = '1.2.840.113556.1.4.1338';
|
||||
|
||||
const LDAP_SERVER_DOMAIN_SCOPE_OID = '1.2.840.113556.1.4.1339';
|
||||
|
||||
const LDAP_SERVER_SEARCH_OPTIONS_OID = '1.2.840.113556.1.4.1340';
|
||||
|
||||
const LDAP_SERVER_PERMISSIVE_MODIFY_OID = '1.2.840.113556.1.4.1413';
|
||||
|
||||
const LDAP_SERVER_ASQ_OID = '1.2.840.113556.1.4.1504';
|
||||
|
||||
const LDAP_SERVER_FAST_BIND_OID = '1.2.840.113556.1.4.1781';
|
||||
|
||||
const LDAP_CONTROL_VLVREQUEST = '2.16.840.1.113730.3.4.9';
|
||||
|
||||
|
||||
// MS Capabilities, Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx
|
||||
|
||||
// Running Active Directory as AD DS
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_OID = '1.2.840.113556.1.4.800';
|
||||
|
||||
// Capable of signing and sealing on an NTLM authenticated connection
|
||||
// and of performing subsequent binds on a signed or sealed connection
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID = '1.2.840.113556.1.4.1791';
|
||||
|
||||
// If AD DS: running at least W2K3, if AD LDS running at least W2K8
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_V51_OID = '1.2.840.113556.1.4.1670';
|
||||
|
||||
// If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST = '1.2.840.113556.1.4.1880';
|
||||
|
||||
// Running Active Directory as AD LDS
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID = '1.2.840.113556.1.4.1851';
|
||||
|
||||
// If AD DS: it's a Read Only DC (RODC)
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID = '1.2.840.113556.1.4.1920';
|
||||
|
||||
// Running at least W2K8
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_V60_OID = '1.2.840.113556.1.4.1935';
|
||||
|
||||
// Running at least W2K8r2
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID = '1.2.840.113556.1.4.2080';
|
||||
|
||||
// Running at least W2K12
|
||||
const LDAP_CAP_ACTIVE_DIRECTORY_W8_OID = '1.2.840.113556.1.4.2237';
|
||||
|
||||
/**
|
||||
* Attributes of the LDAP Server returned by the discovery query
|
||||
*
|
||||
* @var StdClass
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* Map of supported available OIDS
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $oids = array();
|
||||
|
||||
/**
|
||||
* Construct a new capability
|
||||
*
|
||||
* @param $attributes StdClass The attributes returned, may be null for guessing default capabilities
|
||||
*/
|
||||
public function __construct($attributes = null)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
if (isset($attributes->supportedControl)) {
|
||||
foreach ($attributes->supportedControl as $oid) {
|
||||
$this->oids[$oid] = true;
|
||||
}
|
||||
}
|
||||
if (isset($attributes->supportedExtension)) {
|
||||
foreach ($attributes->supportedExtension as $oid) {
|
||||
$this->oids[$oid] = true;
|
||||
}
|
||||
}
|
||||
if (isset($attributes->supportedFeatures)) {
|
||||
foreach ($attributes->supportedFeatures as $oid) {
|
||||
$this->oids[$oid] = true;
|
||||
}
|
||||
}
|
||||
if (isset($attributes->supportedCapabilities)) {
|
||||
foreach ($attributes->supportedCapabilities as $oid) {
|
||||
$this->oids[$oid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the capability object contains support for StartTLS
|
||||
*
|
||||
* @return bool Whether StartTLS is supported
|
||||
*/
|
||||
public function hasStartTLS()
|
||||
{
|
||||
return isset($this->oids[self::LDAP_SERVER_START_TLS_OID]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the capability object contains support for StartTLS
|
||||
*
|
||||
* @return bool Whether StartTLS is supported
|
||||
*/
|
||||
public function hasPagedResult()
|
||||
{
|
||||
return isset($this->oids[self::LDAP_PAGED_RESULT_OID_STRING]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the ldap server is an ActiveDirectory server
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasAdOid()
|
||||
{
|
||||
return isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_OID]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the capability objects contains support for LdapV3, defaults to true if discovery failed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLdapV3()
|
||||
{
|
||||
if (!isset($this->attributes)) {
|
||||
// Default to true, if unknown
|
||||
return true;
|
||||
}
|
||||
|
||||
return (is_string($this->attributes->supportedLDAPVersion)
|
||||
&& (int) $this->attributes->supportedLDAPVersion === 3)
|
||||
|| (is_array($this->attributes->supportedLDAPVersion)
|
||||
&& in_array(3, $this->attributes->supportedLDAPVersion));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the capability with the given OID is supported
|
||||
*
|
||||
* @param $oid string The OID of the capability
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOid($oid)
|
||||
{
|
||||
return isset($this->oids[$oid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default naming context
|
||||
*
|
||||
* @return string|null the default naming context, or null when no contexts are available
|
||||
*/
|
||||
public function getDefaultNamingContext()
|
||||
{
|
||||
// defaultNamingContext entry has higher priority
|
||||
if (isset($this->attributes->defaultNamingContext)) {
|
||||
return $this->attributes->defaultNamingContext;
|
||||
}
|
||||
|
||||
// if its missing use namingContext
|
||||
$namingContexts = $this->namingContexts();
|
||||
return empty($namingContexts) ? null : $namingContexts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the namingContexts
|
||||
*
|
||||
* @return array the available naming contexts
|
||||
*/
|
||||
public function namingContexts()
|
||||
{
|
||||
if (!isset($this->attributes->namingContexts)) {
|
||||
return array();
|
||||
}
|
||||
if (!is_array($this->attributes->namingContexts)) {
|
||||
return array($this->attributes->namingContexts);
|
||||
}
|
||||
return$this->attributes->namingContexts;
|
||||
}
|
||||
}
|
|
@ -44,47 +44,6 @@ class Connection
|
|||
protected $root_dn;
|
||||
protected $count;
|
||||
|
||||
protected $ldap_extension = array(
|
||||
'1.3.6.1.4.1.1466.20037' => 'STARTTLS',
|
||||
// '1.3.6.1.4.1.4203.1.11.1' => '11.1', // PASSWORD_MODIFY
|
||||
// '1.3.6.1.4.1.4203.1.11.3' => '11.3', // Whoami
|
||||
// '1.3.6.1.1.8' => '8', // Cancel Extended Request
|
||||
);
|
||||
|
||||
protected $ms_capability = array(
|
||||
// Prefix LDAP_CAP_
|
||||
// Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx
|
||||
|
||||
// Running Active Directory as AD DS:
|
||||
'1.2.840.113556.1.4.800' => 'ACTIVE_DIRECTORY_OID',
|
||||
|
||||
// Capable of signing and sealing on an NTLM authenticated connection
|
||||
// and of performing subsequent binds on a signed or sealed connection.
|
||||
'1.2.840.113556.1.4.1791' => 'ACTIVE_DIRECTORY_LDAP_INTEG_OID',
|
||||
|
||||
// If AD DS: running at least W2K3, if AD LDS running at least W2K8
|
||||
'1.2.840.113556.1.4.1670' => 'ACTIVE_DIRECTORY_V51_OID',
|
||||
|
||||
// If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals
|
||||
'1.2.840.113556.1.4.1880' => 'ACTIVE_DIRECTORY_ADAM_DIGEST',
|
||||
|
||||
// Running Active Directory as AD LDS
|
||||
'1.2.840.113556.1.4.1851' => 'ACTIVE_DIRECTORY_ADAM_OID',
|
||||
|
||||
// If AD DS: it's a Read Only DC (RODC)
|
||||
'1.2.840.113556.1.4.1920' => 'ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID',
|
||||
|
||||
// Running at least W2K8
|
||||
'1.2.840.113556.1.4.1935' => 'ACTIVE_DIRECTORY_V60_OID',
|
||||
|
||||
// Running at least W2K8r2
|
||||
'1.2.840.113556.1.4.2080' => 'ACTIVE_DIRECTORY_V61_R2_OID',
|
||||
|
||||
// Running at least W2K12
|
||||
'1.2.840.113556.1.4.2237' => 'ACTIVE_DIRECTORY_W8_OID',
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the bind on this connection was already performed
|
||||
*
|
||||
|
@ -94,11 +53,14 @@ class Connection
|
|||
|
||||
protected $root;
|
||||
|
||||
protected $supports_v3 = false;
|
||||
protected $supports_tls = false;
|
||||
|
||||
/**
|
||||
* @var Capability
|
||||
*/
|
||||
protected $capabilities;
|
||||
protected $namingContexts;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $discoverySuccess = false;
|
||||
|
||||
/**
|
||||
|
@ -374,8 +336,8 @@ class Connection
|
|||
$cookie = '';
|
||||
$entries = array();
|
||||
do {
|
||||
// do not set controlPageResult as a critical extension, since we still want the
|
||||
// server to return an answer in case the pagination extension is missing.
|
||||
// do not set controlPageResult as a critical extension, since there is still the possibillity that the
|
||||
// server returns an answer in case the pagination extension is missing.
|
||||
ldap_control_paged_result($this->ds, $pageSize, false, $cookie);
|
||||
|
||||
$results = @ldap_search($this->ds, $base, $queryString, $fields, 0, $limit ? $offset + $limit : 0);
|
||||
|
@ -516,24 +478,18 @@ class Connection
|
|||
|
||||
$ds = ldap_connect($this->hostname, $this->port);
|
||||
try {
|
||||
$capabilities = $this->discoverCapabilities($ds);
|
||||
list($cap, $namingContexts) = $capabilities;
|
||||
$this->capabilities = $this->discoverCapabilities($ds);
|
||||
$this->discoverySuccess = true;
|
||||
|
||||
} catch (LdapException $e) {
|
||||
|
||||
// discovery failed, guess defaults
|
||||
$cap = (object) array(
|
||||
'supports_ldapv3' => true,
|
||||
'supports_starttls' => false,
|
||||
'msCapabilities' => array()
|
||||
);
|
||||
$namingContexts = null;
|
||||
// create empty default capabilities
|
||||
Logger::warning('LADP discovery failed, assuming default LDAP settings.');
|
||||
$this->capabilities = new Capability();
|
||||
}
|
||||
$this->capabilities = $cap;
|
||||
$this->namingContexts = $namingContexts;
|
||||
|
||||
if ($use_tls) {
|
||||
if ($cap->supports_starttls) {
|
||||
if ($this->capabilities->hasStartTLS()) {
|
||||
if (@ldap_start_tls($ds)) {
|
||||
Logger::debug('LDAP STARTTLS succeeded');
|
||||
} else {
|
||||
|
@ -549,11 +505,11 @@ class Connection
|
|||
$this->hostname
|
||||
);
|
||||
} else {
|
||||
// TODO: Log noticy -> TLS enabled but not announced
|
||||
Logger::warning('LDAP TLS enabled but not announced');
|
||||
}
|
||||
}
|
||||
// ldap_rename requires LDAPv3:
|
||||
if ($cap->supports_ldapv3) {
|
||||
if ($this->capabilities->hasLdapV3()) {
|
||||
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
throw new LdapException('LDAPv3 is required');
|
||||
}
|
||||
|
@ -591,141 +547,15 @@ class Connection
|
|||
}
|
||||
|
||||
/**
|
||||
* Return if the capability object contains support for StartTLS
|
||||
* Get the capabilities of the connected server
|
||||
*
|
||||
* @param $cap The object containing the capabilities
|
||||
*
|
||||
* @return bool Whether StartTLS is supported
|
||||
*/
|
||||
protected function hasCapabilityStartTLS($cap)
|
||||
{
|
||||
$cap = $this->getExtensionCapabilities($cap);
|
||||
return isset($cap['1.3.6.1.4.1.1466.20037']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the capability objects contains support for LdapV3
|
||||
*
|
||||
* @param $cap
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasCapabilityLdapV3($cap)
|
||||
{
|
||||
if ((is_string($cap->supportedLDAPVersion)
|
||||
&& (int) $cap->supportedLDAPVersion === 3)
|
||||
|| (is_array($cap->supportedLDAPVersion)
|
||||
&& in_array(3, $cap->supportedLDAPVersion)
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an array of all extension capabilities from the given ldap response
|
||||
*
|
||||
* @param $cap object The response returned by a ldap_search discovery query
|
||||
*
|
||||
* @return object The extracted capabilities.
|
||||
*/
|
||||
protected function getExtensionCapabilities($cap)
|
||||
{
|
||||
$extensions = array();
|
||||
if (isset($cap->supportedExtension)) {
|
||||
foreach ($cap->supportedExtension as $oid) {
|
||||
if (array_key_exists($oid, $this->ldap_extension)) {
|
||||
if ($this->ldap_extension[$oid] === 'STARTTLS') {
|
||||
$extensions['1.3.6.1.4.1.1466.20037'] = $this->ldap_extension['1.3.6.1.4.1.1466.20037'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an array of all MSAD capabilities from the given ldap response
|
||||
*
|
||||
* @param $cap object The response returned by a ldap_search discovery query
|
||||
*
|
||||
* @return object The extracted capabilities.
|
||||
*/
|
||||
protected function getMsCapabilities($cap)
|
||||
{
|
||||
$ms = array();
|
||||
foreach ($this->ms_capability as $name) {
|
||||
$ms[$this->convName($name)] = false;
|
||||
}
|
||||
|
||||
if (isset($cap->supportedCapabilities)) {
|
||||
foreach ($cap->supportedCapabilities as $oid) {
|
||||
if (array_key_exists($oid, $this->ms_capability)) {
|
||||
$ms[$this->convName($this->ms_capability[$oid])] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (object)$ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single capability name entry into camel-case
|
||||
*
|
||||
* @param $name string The name to convert
|
||||
*
|
||||
* @return string The name in camel-case
|
||||
*/
|
||||
private function convName($name)
|
||||
{
|
||||
$parts = explode('_', $name);
|
||||
foreach ($parts as $i => $part) {
|
||||
$parts[$i] = ucfirst(strtolower($part));
|
||||
}
|
||||
return implode('', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the capabilities of this ldap server
|
||||
*
|
||||
* @return stdClass An object, providing the flags 'ldapv3' and 'starttls' to indicate LdapV3 and StartTLS
|
||||
* support and an additional property 'msCapabilities', containing all supported active directory capabilities.
|
||||
* @return Capability The capability object
|
||||
*/
|
||||
public function getCapabilities()
|
||||
{
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default naming context of this ldap connection
|
||||
*
|
||||
* @return string|null the default naming context, or null when no contexts are available
|
||||
*/
|
||||
public function getDefaultNamingContext()
|
||||
{
|
||||
$cap = $this->capabilities;
|
||||
if (isset($cap->defaultNamingContext)) {
|
||||
return $cap->defaultNamingContext;
|
||||
}
|
||||
$namingContexts = $this->namingContexts($cap);
|
||||
return empty($namingContexts) ? null : $namingContexts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the namingContexts for this Ldap-Connection
|
||||
*
|
||||
* @return array the available naming contexts
|
||||
*/
|
||||
public function namingContexts()
|
||||
{
|
||||
if (!isset($this->namingContexts)) {
|
||||
return array();
|
||||
}
|
||||
if (!is_array($this->namingContexts)) {
|
||||
return array($this->namingContexts);
|
||||
}
|
||||
return $this->namingContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether service discovery was successful
|
||||
*
|
||||
|
@ -742,7 +572,7 @@ class Connection
|
|||
*
|
||||
* @param resource $ds The link identifier of the current ldap connection
|
||||
*
|
||||
* @return array The capabilities and naming-contexts
|
||||
* @return Capability The capabilities
|
||||
* @throws LdapException When the capability query fails
|
||||
*/
|
||||
protected function discoverCapabilities($ds)
|
||||
|
@ -759,6 +589,7 @@ class Connection
|
|||
'schemaNamingContext',
|
||||
'supportedLDAPVersion', // => array(3, 2)
|
||||
'supportedCapabilities',
|
||||
'supportedControl',
|
||||
'supportedExtension',
|
||||
'+'
|
||||
)
|
||||
|
@ -789,19 +620,9 @@ class Connection
|
|||
);
|
||||
}
|
||||
|
||||
$cap = (object) array(
|
||||
'supports_ldapv3' => false,
|
||||
'supports_starttls' => false,
|
||||
'msCapabilities' => array()
|
||||
);
|
||||
|
||||
$ldapAttributes = ldap_get_attributes($ds, $entry);
|
||||
$result = $this->cleanupAttributes($ldapAttributes);
|
||||
$cap->supports_ldapv3 = $this->hasCapabilityLdapV3($result);
|
||||
$cap->supports_starttls = $this->hasCapabilityStartTLS($result);
|
||||
$cap->msCapabilities = $this->getMsCapabilities($result);
|
||||
|
||||
return array($cap, $result->namingContexts);
|
||||
return new Capability($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,7 +54,7 @@ class Discovery {
|
|||
return array(
|
||||
'hostname' => $this->connection->getHostname(),
|
||||
'port' => $this->connection->getPort(),
|
||||
'root_dn' => $this->connection->getDefaultNamingContext()
|
||||
'root_dn' => $this->connection->getCapabilities()->getDefaultNamingContext()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -69,14 +69,14 @@ class Discovery {
|
|||
$this->execDiscovery();
|
||||
if ($this->isAd()) {
|
||||
return array(
|
||||
'base_dn' => $this->connection->getDefaultNamingContext(),
|
||||
'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
|
||||
'user_class' => 'user',
|
||||
'user_name_attribute' => 'sAMAccountName'
|
||||
);
|
||||
} else {
|
||||
return array(
|
||||
'base_dn' => $this->connection->getDefaultNamingContext(),
|
||||
'user_class' => 'getDefaultNamingContext',
|
||||
'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
|
||||
'user_class' => 'inetOrgPerson',
|
||||
'user_name_attribute' => 'uid'
|
||||
);
|
||||
}
|
||||
|
@ -90,8 +90,7 @@ class Discovery {
|
|||
public function isAd()
|
||||
{
|
||||
$this->execDiscovery();
|
||||
$caps = $this->connection->getCapabilities();
|
||||
return isset($caps->msCapabilities->ActiveDirectoryOid) && $caps->msCapabilities->ActiveDirectoryOid;
|
||||
return $this->connection->getCapabilities()->hasAdOid();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue