Merge branch 'master' into bugfix/drop-zend-config-7147

Conflicts:
	application/forms/LdapDiscoveryForm.php
This commit is contained in:
Johannes Meyer 2014-11-18 13:21:18 +01:00
commit c288a68ed5
19 changed files with 288 additions and 185 deletions

View File

@ -602,14 +602,14 @@ exec { 'create-pgsql-icingaweb-db':
} }
exec { 'populate-icingaweb-mysql-db-tables': exec { 'populate-icingaweb-mysql-db-tables':
unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM account;" &> /dev/null', unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM icingaweb_group;" &> /dev/null',
command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/mysql.sql', command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/mysql.schema.sql',
require => [ Exec['create-mysql-icingaweb-db'] ] require => [ Exec['create-mysql-icingaweb-db'] ]
} }
exec { 'populate-icingweba-pgsql-db-tables': exec { 'populate-icingweba-pgsql-db-tables':
unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM account;" &> /dev/null', unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM icingaweb_group;" &> /dev/null',
command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/pgsql.sql', command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/pgsql.schema.sql',
require => [ Exec['create-pgsql-icingaweb-db'] ] require => [ Exec['create-pgsql-icingaweb-db'] ]
} }

1
Vagrantfile vendored
View File

@ -54,7 +54,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# the path on the host to the actual folder. The second argument is # the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third # the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options. # argument is a set of non-required options.
config.vm.synced_folder "./config", "/vagrant/config"
config.vm.synced_folder "./var/log", "/vagrant/var/log" config.vm.synced_folder "./var/log", "/vagrant/var/log"
# Provider-specific configuration so you can fine-tune various # Provider-specific configuration so you can fine-tune various

View File

@ -1,47 +1,13 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Forms; namespace Icinga\Forms;
use Icinga\Application\Logger;
use Icinga\Data\ConfigObject;
use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Protocol\Ldap\Connection;
use Icinga\Protocol\Dns;
use Icinga\Web\Form; use Icinga\Web\Form;
/**
* Form class for application-wide and logging specific settings
*/
class LdapDiscoveryForm extends Form 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 * Initialize this page
*/ */
@ -104,108 +70,6 @@ class LdapDiscoveryForm extends Form
if (false === parent::isValid($data)) { if (false === parent::isValid($data)) {
return false; return false;
} }
if ($this->discover($this->getValue('domain'))) {
return true;
}
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 ConfigObject($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

@ -1,5 +1,4 @@
#!/usr/bin/php #!/usr/bin/php
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}

View File

@ -76,6 +76,7 @@ CREATE UNIQUE INDEX idx_icingaweb_user
CREATE TABLE "icingaweb_user_preference" ( CREATE TABLE "icingaweb_user_preference" (
"username" character varying(64) NOT NULL, "username" character varying(64) NOT NULL,
"name" character varying(64) NOT NULL, "name" character varying(64) NOT NULL,
"section" character varying(64) NOT NULL,
"value" character varying(255) NOT NULL, "value" character varying(255) NOT NULL,
"ctime" timestamp NULL DEFAULT NULL, "ctime" timestamp NULL DEFAULT NULL,
"mtime" timestamp NULL DEFAULT NULL "mtime" timestamp NULL DEFAULT NULL
@ -85,6 +86,7 @@ ALTER TABLE ONLY "icingaweb_user_preference"
ADD CONSTRAINT pk_icingaweb_user_preference ADD CONSTRAINT pk_icingaweb_user_preference
PRIMARY KEY ( PRIMARY KEY (
"username", "username",
"section",
"name" "name"
); );
@ -92,5 +94,6 @@ CREATE UNIQUE INDEX idx_icingaweb_user_preference
ON "icingaweb_user_preference" ON "icingaweb_user_preference"
USING btree ( USING btree (
lower((username)::text), lower((username)::text),
lower((section)::text),
lower((name)::text) lower((name)::text)
); );

View File

@ -95,6 +95,7 @@ class Connection
protected $capabilities; protected $capabilities;
protected $namingContexts; protected $namingContexts;
protected $discoverySuccess = false;
/** /**
* Constructor * Constructor
@ -112,6 +113,16 @@ class Connection
$this->port = $config->get('port', $this->port); $this->port = $config->get('port', $this->port);
} }
public function getHostname()
{
return $this->hostname;
}
public function getPort()
{
return $this->port;
}
public function getDN() public function getDN()
{ {
return $this->root_dn; return $this->root_dn;
@ -391,6 +402,7 @@ class Connection
try { try {
$capabilities = $this->discoverCapabilities($ds); $capabilities = $this->discoverCapabilities($ds);
list($cap, $namingContexts) = $capabilities; list($cap, $namingContexts) = $capabilities;
$this->discoverySuccess = true;
} catch (LdapException $e) { } catch (LdapException $e) {
// discovery failed, guess defaults // discovery failed, guess defaults
@ -602,6 +614,17 @@ class Connection
return $this->namingContexts; 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 * 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\Data\ConfigObject;
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 ConfigObject(array(
'hostname' => $host,
'port' => $port
)));
return new Discovery($conn);
}
}

View File

@ -428,7 +428,7 @@ class ActionController extends Zend_Controller_Action
} }
if ($this->autorefreshInterval !== null) { if ($this->autorefreshInterval !== null) {
// $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval); $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval);
} }
} }

View File

@ -11,7 +11,6 @@ use Zend_View_Interface;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Form\Decorator\NoScriptApply; use Icinga\Web\Form\Decorator\NoScriptApply;
use Icinga\Web\Form\Element\CsrfCounterMeasure; use Icinga\Web\Form\Element\CsrfCounterMeasure;
use Icinga\Web\Form\FormElement;
/** /**
* Base class for forms providing CSRF protection, confirmation logic and auto submission * Base class for forms providing CSRF protection, confirmation logic and auto submission
@ -139,6 +138,21 @@ class Form extends Zend_Form
throw new LogicException('The option `onSuccess\' is not callable'); throw new LogicException('The option `onSuccess\' is not callable');
} }
// Zend's plugin loader reverses the order of added prefix paths thus trying our paths first before trying
// Zend paths
$this->addPrefixPaths(array(
array(
'prefix' => 'Icinga\\Web\\Form\\Element\\',
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Element'),
'type' => static::ELEMENT
),
array(
'prefix' => 'Icinga\\Web\\Form\\Decorator\\',
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Decorator'),
'type' => static::DECORATOR
)
));
parent::__construct($options); parent::__construct($options);
} }
@ -464,9 +478,7 @@ class Form extends Zend_Form
$options = array('decorators' => static::$defaultElementDecorators); $options = array('decorators' => static::$defaultElementDecorators);
} }
if (($el = $this->createIcingaFormElement($type, $name, $options)) === null) { $el = parent::createElement($type, $name, $options);
$el = parent::createElement($type, $name, $options);
}
if ($el && $el->getAttrib('autosubmit')) { if ($el && $el->getAttrib('autosubmit')) {
$noScript = new NoScriptApply(); // Non-JS environments $noScript = new NoScriptApply(); // Non-JS environments
@ -738,25 +750,6 @@ class Form extends Zend_Form
return array(); return array();
} }
/**
* Create a new element located in the Icinga Web 2 library
*
* @param string $type The type of the element
* @param string $name The name of the element
* @param mixed $options The options for the element
*
* @return NULL|FormElement NULL in case the element is not found in the Icinga Web 2 library
*
* @see Form::$defaultElementDecorators For Icinga Web 2's default element decorators.
*/
protected function createIcingaFormElement($type, $name, $options = null)
{
$className = 'Icinga\\Web\\Form\\Element\\' . ucfirst($type);
if (class_exists($className)) {
return new $className($name, $options);
}
}
/** /**
* Render this form * Render this form
* *

View File

@ -47,7 +47,6 @@ class Number extends FormElement
*/ */
public function init() public function init()
{ {
$this->addValidator('Float', true); // true for breaking the validator chain on failure
if ($this->min !== null) { if ($this->min !== null) {
$this->addValidator('GreaterThan', true, array('min' => $this->min)); $this->addValidator('GreaterThan', true, array('min' => $this->min));
} }
@ -127,4 +126,19 @@ class Number extends FormElement
{ {
return $this->step; return $this->step;
} }
/**
* (non-PHPDoc)
* @see \Zend_Form_Element::isValid() For the method documentation.
*/
public function isValid($value, $context = null)
{
$this->setValue($value);
$value = $this->getValue();
if (! is_numeric($value)) {
$this->addError(sprintf($this->translate('\'%s\' is not a valid number'), $value));
return false;
}
return parent::isValid($value, $context);
}
} }

View File

@ -2,11 +2,19 @@
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Controller; use Icinga\Web\Controller;
use Icinga\Web\Widget;
class Doc_StyleController extends Controller class Doc_StyleController extends Controller
{ {
public function fontAction() public function fontAction()
{ {
$this->view->tabs = Widget::create('tabs')->add(
'fonts',
array(
'title' => $this->translate('Icons'),
'url' => 'doc/style/font'
)
)->activate('fonts');
$confFile = Icinga::app()->getApplicationDir('fonts/fontanello-ifont/config.json'); $confFile = Icinga::app()->getApplicationDir('fonts/fontanello-ifont/config.json');
$this->view->font = json_decode(file_get_contents($confFile)); $this->view->font = json_decode(file_get_contents($confFile));
} }

View File

@ -0,0 +1,3 @@
<div class="chapter">
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
</div>

View File

@ -0,0 +1,6 @@
<div class="controls">
<h1><?= $title ?></h1>
</div>
<div class="content toc">
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
</div>

View File

@ -1,4 +1,5 @@
<div class="controls"> <div class="controls">
<?= $this->tabs ?>
<h1>Icinga Web 2 Icons</h1> <h1>Icinga Web 2 Icons</h1>
</div> </div>

View File

@ -17,7 +17,7 @@ $section->add('Icinga Web 2', array(
$section->add('Module documentations', array( $section->add('Module documentations', array(
'url' => 'doc/module', 'url' => 'doc/module',
)); ));
$section->add($this->translate('Fonts'), array( $section->add($this->translate('Developer - Style'), array(
'url' => 'doc/style/font', 'url' => 'doc/style/font',
'priority' => 200, 'priority' => 200,
)); ));

View File

@ -7,11 +7,15 @@ if ($object->getType() === $object::TYPE_HOST) {
'monitoring/host/reschedule-check', 'monitoring/host/reschedule-check',
array('host' => $object->getName()) array('host' => $object->getName())
); );
$checkAttempts = $object->host_current_check_attempt . '/' . $object->host_max_check_attempts;
$stateType = (int) $object->host_state_type;
} else { } else {
$reschedule = $this->href( $reschedule = $this->href(
'monitoring/service/reschedule-check', 'monitoring/service/reschedule-check',
array('host' => $object->getHost()->getName(), 'service' => $object->getName()) array('host' => $object->getHost()->getName(), 'service' => $object->getName())
); );
$checkAttempts = $object->service_attempt;
$stateType = (int) $object->service_state_type;
} }
?> ?>
@ -32,6 +36,14 @@ if ($object->getType() === $object::TYPE_HOST) {
<?= $this->timeUntil($object->next_check) ?> <?= $this->timeUntil($object->next_check) ?>
</td> </td>
</tr> </tr>
<tr>
<th><?= $this->translate('Check attempts') ?></th>
<?php if ($stateType === 0): ?>
<td><?= $checkAttempts ?> <?= $this->translate('(soft state)') ?></td>
<?php else: ?>
<td><?= $checkAttempts ?> <?= $this->translate('(hard state)') ?></td>
<?php endif; ?>
</tr>
<?php if ($object->check_execution_time): ?> <?php if ($object->check_execution_time): ?>
<tr> <tr>
<th><?= $this->translate('Check execution time') ?></th> <th><?= $this->translate('Check execution time') ?></th>

View File

@ -6,7 +6,12 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
?> ?>
<h3 class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : '' ?>> <h3 class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : '' ?>>
<?php if ($object->stats->services_total > 0): ?>
<?= $this->qlink(sprintf($this->translate('%s configured services:'), $object->stats->services_total), $selfUrl) ?> <?= $this->qlink(sprintf($this->translate('%s configured services:'), $object->stats->services_total), $selfUrl) ?>
<?php else: ?>
<?= $this->translate('No services configured on this host'); ?>
<?php endif; ?>
<?php if ($object->stats->services_ok > 0): ?> <?php if ($object->stats->services_ok > 0): ?>
<span class="state ok<?= $currentUrl === $selfUrl->with('service_state', 0)->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink( <span class="state ok<?= $currentUrl === $selfUrl->with('service_state', 0)->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
$object->stats->services_ok, $object->stats->services_ok,

View File

@ -6,6 +6,8 @@ namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Forms\LdapDiscoveryForm; 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 * Wizard page to define the connection details for a LDAP resource
@ -13,9 +15,9 @@ use Icinga\Forms\LdapDiscoveryForm;
class LdapDiscoveryPage extends Form class LdapDiscoveryPage extends Form
{ {
/** /**
* @var LdapDiscoveryForm * @var Discovery
*/ */
private $discoveryForm; private $discovery;
/** /**
* Initialize this page * Initialize this page
@ -53,8 +55,8 @@ class LdapDiscoveryPage extends Form
) )
); );
$this->discoveryForm = new LdapDiscoveryForm(); $discoveryForm = new LdapDiscoveryForm();
$this->addElements($this->discoveryForm->createElements($formData)->getElements()); $this->addElements($discoveryForm->createElements($formData)->getElements());
$this->getElement('domain')->setRequired( $this->getElement('domain')->setRequired(
isset($formData['skip_validation']) === false || ! $formData['skip_validation'] isset($formData['skip_validation']) === false || ! $formData['skip_validation']
); );
@ -82,25 +84,38 @@ class LdapDiscoveryPage extends Form
if (false === parent::isValid($data)) { if (false === parent::isValid($data)) {
return false; return false;
} }
if ($data['skip_validation']) {
if (! $data['skip_validation'] && false === $this->discoveryForm->isValid($data)) { return true;
return false;
} }
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) public function getValues($suppressArrayNotation = false)
{ {
if (! isset($this->discoveryForm) || ! $this->discoveryForm->hasSuggestion()) { if (! isset($this->discovery) || ! $this->discovery->isSuccess()) {
return null; return null;
} }
$disc = $this->discovery;
return array( return array(
'domain' => $this->getValue('domain'), 'domain' => $this->getValue('domain'),
'type' => $this->discoveryForm->isAd() ? 'type' => $disc->isAd() ? LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC,
LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC, 'resource' => $disc->suggestResourceSettings(),
'resource' => $this->discoveryForm->suggestResourceSettings(), 'backend' => $disc->suggestBackendSettings()
'backend' => $this->discoveryForm->suggestBackendSettings()
); );
} }
} }

View File

@ -8,7 +8,7 @@ ul.tabs {
.controls ul.tabs { .controls ul.tabs {
margin-left: 0em; margin-left: 0em;
margin-top: -3.6em; margin-top: -3.54em;
height: 2.6em; height: 2.6em;
overflow: hidden; overflow: hidden;
} }