Fix Ldap server discovery

Add a new connection member that stores whether settings were guessed or product of a discovery, move discovery methods into seperate class.

fixes #7691
This commit is contained in:
Matthias Jentsch 2014-11-18 09:45:54 +01:00
parent cbcea2a3c3
commit 5f8fcf4005
4 changed files with 211 additions and 146 deletions

View File

@ -1,4 +1,6 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Forms;
@ -9,39 +11,8 @@ use Icinga\Protocol\Ldap\Connection;
use Icinga\Protocol\Dns;
use Icinga\Web\Form;
/**
* Form class for application-wide and logging specific settings
*/
class LdapDiscoveryForm extends Form
{
/**
* The discovered server settings
*
* @var array
*/
private $capabilities = null;
/**
* The discovered root_dn
*
* @var null
*/
private $namingContext = null;
/**
* The working domain name
*
* @var null
*/
private $domain = null;
/**
* The working port name
*
* @var int
*/
private $port = 389;
/**
* Initialize this page
*/
@ -104,108 +75,6 @@ class LdapDiscoveryForm extends Form
if (false === parent::isValid($data)) {
return false;
}
if ($this->discover($this->getValue('domain'))) {
return true;
}
return true;
}
private function discover($domain)
{
// Attempt 1: Connect to the domain directly
if ($this->discoverCapabilities(array(
'hostname' => $domain,
'port' => 389)
)) {
return true;
}
// Attempt 2: Discover all available ldap dns records and connect to the first one
$cap = false;
$records = array_merge(Dns::getSrvRecords($domain, 'ldap'), Dns::getSrvRecords($domain, 'ldaps'));
if (isset($records[0])) {
$record = $records[0];
if (isset($record['port'])) {
$cap = $this->discoverCapabilities(array(
'hostname' => $record['target'],
'port' => $record['port']
));
} else {
$cap = $this->discoverCapabilities(array(
'hostname' => $record['target'],
'port' => 389
));
}
}
return $cap;
}
private function discoverCapabilities($config)
{
$conn = new Connection(new Config($config));
try {
$conn->connect();
$this->capabilities = $conn->getCapabilities();
$this->namingContext = $conn->getDefaultNamingContext();
$this->port = $config['port'];
$this->domain = $config['hostname'];
return true;
} catch (LdapException $e) {
Logger::info(
'Ldap discovery for ' . $config['hostname'] . ':' . $config['port'] . ' failed: ' . $e->getMessage()
);
return false;
}
}
public function suggestResourceSettings()
{
if (! isset($this->capabilities)) {
return array();
}
if ($this->capabilities->msCapabilities->ActiveDirectoryOid) {
return array(
'hostname' => $this->domain,
'port' => $this->port,
'root_dn' => $this->namingContext
);
} else {
return array(
'hostname' => $this->domain,
'port' => $this->port,
'root_dn' => $this->namingContext
);
}
}
public function hasSuggestion()
{
return isset($this->capabilities);
}
public function suggestBackendSettings()
{
if (! isset($this->capabilities)) {
return array();
}
if ($this->capabilities->msCapabilities->ActiveDirectoryOid) {
return array(
'base_dn' => $this->namingContext,
'user_class' => 'user',
'user_name_attribute' => 'sAMAccountName'
);
} else {
return array(
'base_dn' => $this->namingContext,
'user_class' => 'inetOrgPerson',
'user_name_attribute' => 'uid'
);
}
}
public function isAd()
{
return $this->capabilities->msCapabilities->ActiveDirectoryOid;
}
}

View File

@ -94,6 +94,7 @@ class Connection
protected $capabilities;
protected $namingContexts;
protected $discoverySuccess = false;
/**
* Constructor
@ -111,6 +112,16 @@ class Connection
$this->port = $config->get('port', $this->port);
}
public function getHostname()
{
return $this->hostname;
}
public function getPort()
{
return $this->port;
}
public function getDN()
{
return $this->root_dn;
@ -390,6 +401,7 @@ class Connection
try {
$capabilities = $this->discoverCapabilities($ds);
list($cap, $namingContexts) = $capabilities;
$this->discoverySuccess = true;
} catch (LdapException $e) {
// discovery failed, guess defaults
@ -601,6 +613,17 @@ class Connection
return $this->namingContexts;
}
/**
* Whether service discovery was successful
*
* @return boolean True when ldap settings were discovered, false when
* settings were guessed
*/
public function discoverySuccessful()
{
return $this->discoverySuccess;
}
/**
* Discover the capabilities of the given ldap-server
*

View File

@ -0,0 +1,158 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Protocol\Ldap;
use Icinga\Application\Config;
use Icinga\Protocol\Dns;
class Discovery {
/**
* @var Connection
*/
private $connection;
/**
* If discovery was already performed
*
* @var bool
*/
private $discovered = false;
/**
* @param Connection $conn The ldap connection to use for the discovery
*/
public function __construct(Connection $conn)
{
$this->connection = $conn;
}
/**
* Execute the discovery on the underlying connection
*/
private function execDiscovery()
{
if (! $this->discovered) {
$this->connection->connect();
$this->discovered = true;
}
}
/**
* Suggests a resource configuration of hostname, port and root_dn
* based on the discovery
*
* @return array The suggested configuration as an array
*/
public function suggestResourceSettings()
{
if (! $this->discovered) {
$this->execDiscovery();
}
return array(
'hostname' => $this->connection->getHostname(),
'port' => $this->connection->getPort(),
'root_dn' => $this->connection->getDefaultNamingContext()
);
}
/**
* Suggests a backend configuration of base_dn, user_class and user_name_attribute
* based on the discovery
*
* @return array The suggested configuration as an array
*/
public function suggestBackendSettings()
{
$this->execDiscovery();
if ($this->isAd()) {
return array(
'base_dn' => $this->connection->getDefaultNamingContext(),
'user_class' => 'user',
'user_name_attribute' => 'sAMAccountName'
);
} else {
return array(
'base_dn' => $this->connection->getDefaultNamingContext(),
'user_class' => 'getDefaultNamingContext',
'user_name_attribute' => 'uid'
);
}
}
/**
* Whether the suggested ldap server is an ActiveDirectory
*
* @return boolean
*/
public function isAd()
{
$this->execDiscovery();
$caps = $this->connection->getCapabilities();
return isset($caps->msCapabilities->ActiveDirectoryOid) && $caps->msCapabilities->ActiveDirectoryOid;
}
/**
* Whether the discovery was successful
*
* @return bool False when the suggestions are guessed
*/
public function isSuccess()
{
$this->execDiscovery();
return $this->connection->discoverySuccessful();
}
/**
* Discover LDAP servers on the given domain
*
* @param string $domain The object containing the form elements
*
* @return Discovery True when the discovery was successful, false when the configuration was guessed
*/
public static function discoverDomain($domain)
{
if (! isset($domain)) {
return false;
}
// Attempt 1: Connect to the domain directly
$disc = Discovery::discover($domain, 389);
if ($disc->isSuccess()) {
return $disc;
}
// Attempt 2: Discover all available ldap dns records and connect to the first one
$records = array_merge(Dns::getSrvRecords($domain, 'ldap'), Dns::getSrvRecords($domain, 'ldaps'));
if (isset($records[0])) {
$record = $records[0];
return Discovery::discover(
isset($record['target']) ? $record['target'] : $domain,
isset($record['port']) ? $record['port'] : $domain
);
}
// Return the first failed discovery, which will suggest properties based on guesses
return $disc;
}
/**
* Convenience method to instantiate a new Discovery
*
* @param $host The host on which to execute the discovery
* @param $port The port on which to execute the discovery
*
* @return Discover The resulting Discovery
*/
public static function discover($host, $port)
{
$conn = new Connection(new Config(array(
'hostname' => $host,
'port' => $port
)));
return new Discovery($conn);
}
}

View File

@ -6,6 +6,8 @@ namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form;
use Icinga\Forms\LdapDiscoveryForm;
use Icinga\Protocol\Ldap\Discovery;
use Icinga\Module\Setup\Forms\LdapDiscoveryConfirmPage;
/**
* Wizard page to define the connection details for a LDAP resource
@ -13,9 +15,9 @@ use Icinga\Forms\LdapDiscoveryForm;
class LdapDiscoveryPage extends Form
{
/**
* @var LdapDiscoveryForm
* @var Discovery
*/
private $discoveryForm;
private $discovery;
/**
* Initialize this page
@ -53,8 +55,8 @@ class LdapDiscoveryPage extends Form
)
);
$this->discoveryForm = new LdapDiscoveryForm();
$this->addElements($this->discoveryForm->createElements($formData)->getElements());
$discoveryForm = new LdapDiscoveryForm();
$this->addElements($discoveryForm->createElements($formData)->getElements());
$this->getElement('domain')->setRequired(
isset($formData['skip_validation']) === false || ! $formData['skip_validation']
);
@ -82,25 +84,38 @@ class LdapDiscoveryPage extends Form
if (false === parent::isValid($data)) {
return false;
}
if (! $data['skip_validation'] && false === $this->discoveryForm->isValid($data)) {
return false;
if ($data['skip_validation']) {
return true;
}
return true;
if (isset($data['domain'])) {
$this->discovery = Discovery::discoverDomain($data['domain']);
if ($this->discovery->isSuccess()) {
return true;
}
}
$this->addError(sprintf(t('Could not find any LDAP servers on the domain "%s".'), $data['domain']));
return false;
}
/**
* Suggest settings based on the underlying discovery
*
* @param bool $suppressArrayNotation
*
* @return array|null
*/
public function getValues($suppressArrayNotation = false)
{
if (! isset($this->discoveryForm) || ! $this->discoveryForm->hasSuggestion()) {
if (! isset($this->discovery) || ! $this->discovery->isSuccess()) {
return null;
}
$disc = $this->discovery;
return array(
'domain' => $this->getValue('domain'),
'type' => $this->discoveryForm->isAd() ?
LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC,
'resource' => $this->discoveryForm->suggestResourceSettings(),
'backend' => $this->discoveryForm->suggestBackendSettings()
'type' => $disc->isAd() ? LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC,
'resource' => $disc->suggestResourceSettings(),
'backend' => $disc->suggestBackendSettings()
);
}
}