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:
Matthias Jentsch 2015-02-27 09:51:44 +01:00
parent 7bb78330a9
commit e93a5f16d9
3 changed files with 245 additions and 207 deletions

View File

@ -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;
}
}

View File

@ -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);
}
/**

View File

@ -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();
}
/**