From 4587f26476c10a48a74055e4ffaf745c970bae82 Mon Sep 17 00:00:00 2001
From: Marius Hein
Date: Fri, 6 Jun 2014 14:41:57 +0200
Subject: [PATCH 01/29] Revert "Ui/Sparklines: Remove img src before putting it
into DOM"
This reverts commit a75796c64d397d37f1d41ae3d7e6bdf119b340d9.
---
public/js/icinga/loader.js | 2 +-
public/js/icinga/ui.js | 16 ----------------
2 files changed, 1 insertion(+), 17 deletions(-)
diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js
index 338629136..3b92e5b2f 100644
--- a/public/js/icinga/loader.js
+++ b/public/js/icinga/loader.js
@@ -269,7 +269,7 @@
if (this.processRedirectHeader(req)) return;
// div helps getting an XML tree
- var $resp = $('' + icinga.ui.removeImageSourceFromSparklines(req.responseText) + '
');
+ var $resp = $('' + req.responseText + '
');
var active = false;
var rendered = false;
var classes;
diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js
index 6902953ca..e76e2dba8 100644
--- a/public/js/icinga/ui.js
+++ b/public/js/icinga/ui.js
@@ -657,22 +657,6 @@
);
},
- /**
- * Find all svg charts and removes src attributes for sparklines
- *
- * @param {string} text
- * @returns {string}
- */
- removeImageSourceFromSparklines: function(text) {
- var match, sourceMatch;
- var re = new RegExp(/(src=".+chart.php[^"]+")/g);
- var reSource = new RegExp(/src="([^"]+)"/);
- while ((match = re.exec(text))) {
- text = text.replace(match[0], '');
- }
- return text;
- },
-
initializeControls: function (parent) {
var self = this;
From 06296f29d8d74b4122e245d01371760e2af3df6e Mon Sep 17 00:00:00 2001
From: Marius Hein
Date: Fri, 6 Jun 2014 16:35:33 +0200
Subject: [PATCH 02/29] UI/Sparklines: Change sparkline code to serverside only
fixes #6124
---
library/Icinga/Web/Widget/Chart/InlinePie.php | 4 +++
public/js/icinga/events.js | 4 ---
public/js/icinga/loader.js | 5 ----
public/js/icinga/ui.js | 26 -------------------
4 files changed, 4 insertions(+), 35 deletions(-)
diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php
index 48416b8ec..c219ab4d7 100644
--- a/library/Icinga/Web/Widget/Chart/InlinePie.php
+++ b/library/Icinga/Web/Widget/Chart/InlinePie.php
@@ -50,10 +50,14 @@ class InlinePie extends AbstractWidget
* @var string
*/
private $template =<<<'EOD'
+
+
EOD;
/**
diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js
index e8bfdcfc6..1b0178d31 100644
--- a/public/js/icinga/events.js
+++ b/public/js/icinga/events.js
@@ -69,10 +69,6 @@
$('input.autofocus', el).focus();
- $('img.inlinepie', el).each(function() {
- icinga.ui.initializeSparklines($(this));
- });
-
// replace all sparklines
$('span.sparkline', el).sparkline('html', { enableTagOptions: true });
diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js
index 3b92e5b2f..918b890d8 100644
--- a/public/js/icinga/loader.js
+++ b/public/js/icinga/loader.js
@@ -409,11 +409,6 @@
this.icinga.ui.initializeTriStates($resp);
- // Replace images with sparklines.
- $resp.find('img.inlinepie').each(function(){
- self.icinga.ui.initializeSparklines($(this));
- });
-
/* Should we try to fiddle with responses containing full HTML? */
/*
if ($('body', $resp).length) {
diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js
index e76e2dba8..cca166126 100644
--- a/public/js/icinga/ui.js
+++ b/public/js/icinga/ui.js
@@ -631,32 +631,6 @@
}
},
- /**
- * Search and replace all inlinepies with html for sparklines.
- *
- * @param parent
- */
- initializeSparklines: function($container) {
-
- // replace all remaining images with sparklines
- var title = $container.attr('title'),
- values = $container.data('icinga-values'),
- colors = $container.data('icinga-colors'),
- width = $container.css('width'),
- height = $container.css('height');
- if (!values) {
- return;
- }
- $container.replaceWith(
- ''
- );
- },
-
initializeControls: function (parent) {
var self = this;
From 953d22244a7cd81b188d1263758c6b88af4aa3fc Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Fri, 6 Jun 2014 15:39:01 +0200
Subject: [PATCH 03/29] Add host discovery to authentication page
Find all domains for a given hostname.
refs #6093
Conflicts:
application/forms/Install/AuthenticationPage.php
---
library/Icinga/Protocol/Ldap/Connection.php | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php
index 13ae19fd9..70c97055d 100644
--- a/library/Icinga/Protocol/Ldap/Connection.php
+++ b/library/Icinga/Protocol/Ldap/Connection.php
@@ -353,10 +353,18 @@ class Connection
return $dir;
}
- protected function discoverServerlistForDomain($domain)
+ public static function discoverServerlistForDomain($domain)
{
+ $domains = array();
$ldaps_records = dns_get_record('_ldaps._tcp.' . $domain, DNS_SRV);
+ foreach ($ldaps_records as $record) {
+ $domains[$record['target']] = true;
+ }
$ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
+ foreach ($ldap_records as $record) {
+ $domains[$record['target']] = true;
+ }
+ return array_keys($domains);
}
protected function prepareNewConnection()
From efe67377a83daffa21344bc2ba4e78b31ae01653 Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Fri, 6 Jun 2014 17:45:24 +0200
Subject: [PATCH 04/29] Move dns discovery functions into separate class
Add functions to lookup and reverse-lookup domain names and move the ldap
discovery function into a separate class
refs #6093
---
library/Icinga/Protocol/Dns.php | 114 ++++++++++++++++++++
library/Icinga/Protocol/Ldap/Connection.php | 14 ---
2 files changed, 114 insertions(+), 14 deletions(-)
create mode 100644 library/Icinga/Protocol/Dns.php
diff --git a/library/Icinga/Protocol/Dns.php b/library/Icinga/Protocol/Dns.php
new file mode 100644
index 000000000..b2807ac5d
--- /dev/null
+++ b/library/Icinga/Protocol/Dns.php
@@ -0,0 +1,114 @@
+
+ * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
+ * @author Icinga Development Team
+ *
+ */
+// {{{ICINGA_LICENSE_HEADER}}}
+
+namespace Icinga\Protocol;
+
+/**
+ * Discover dns records using regular or reverse lookup
+ */
+class Dns {
+
+ /**
+ * Get all ldap records for the given domain
+ *
+ * @param String $query The domain to query
+ *
+ * @return array An array of entries
+ */
+ public static function ldapRecords($query)
+ {
+ $ldaps_records = dns_get_record('_ldaps._tcp.' . $query);
+ $ldap_records = dns_get_record('_ldap._tcp.' . $query);
+ return array_merge($ldaps_records, $ldap_records);
+ }
+
+ /**
+ * Get all ldap records for the given domain
+ *
+ * @param String $query The domain to query
+ * @param int $type The type of DNS-entry to fetch, see http://www.php.net/manual/de/function.dns-get-record.php
+ * for available types
+ *
+ * @return array|Boolean An array of entries
+ */
+ public static function records($query, $type = DNS_ANY)
+ {
+ return dns_get_record($query, $type);
+ }
+
+ /**
+ * Reverse lookup all hostname on the given ip address
+ *
+ * @param $ipAddress
+ * @param int $type
+ *
+ * @return array|Boolean
+ */
+ public static function ptr($ipAddress, $type = DNS_ANY)
+ {
+ $host = gethostbyaddr($ipAddress);
+ if ($host === false || $host === $ipAddress) {
+ // malformed input or no host found
+ return false;
+ }
+ return self::records($host, $type);
+ }
+
+ /**
+ * Get the IPv4 address of the given hostname.
+ *
+ * @param $hostname The hostname to resolve
+ *
+ * @return String|Boolean The IPv4 address of the given hostname, or false when no entry exists.
+ */
+ public static function ipv4($hostname)
+ {
+ $records = dns_get_record($hostname, DNS_A);
+ if ($records !== false && sizeof($records) > 0) {
+ return $records[0]['ip'];
+ }
+ return false;
+ }
+
+ /**
+ * Get the IPv6 address of the given hostname.
+ *
+ * @param $hostname The hostname to resolve
+ *
+ * @return String|Boolean The IPv6 address of the given hostname, or false when no entry exists.
+ */
+ public static function ipv6($hostname)
+ {
+ $records = dns_get_record($hostname, DNS_AAAA);
+ if ($records !== false && sizeof($records) > 0) {
+ return $records[0]['ip'];
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php
index 70c97055d..54cc05f53 100644
--- a/library/Icinga/Protocol/Ldap/Connection.php
+++ b/library/Icinga/Protocol/Ldap/Connection.php
@@ -353,20 +353,6 @@ class Connection
return $dir;
}
- public static function discoverServerlistForDomain($domain)
- {
- $domains = array();
- $ldaps_records = dns_get_record('_ldaps._tcp.' . $domain, DNS_SRV);
- foreach ($ldaps_records as $record) {
- $domains[$record['target']] = true;
- }
- $ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
- foreach ($ldap_records as $record) {
- $domains[$record['target']] = true;
- }
- return array_keys($domains);
- }
-
protected function prepareNewConnection()
{
$use_tls = false;
From 305a025e7adb58d2573f4af2d6a3a7f203d3d684 Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Fri, 6 Jun 2014 17:57:50 +0200
Subject: [PATCH 05/29] Detect ldap default naming context
Read the entries in the RootDTS of the given ldap server, to discover
its default naming context and capabilities
refs #6097
refs #6096
---
library/Icinga/Protocol/Ldap/Connection.php | 179 +++++++++++++++-----
1 file changed, 136 insertions(+), 43 deletions(-)
diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php
index 54cc05f53..6419fbb6f 100644
--- a/library/Icinga/Protocol/Ldap/Connection.php
+++ b/library/Icinga/Protocol/Ldap/Connection.php
@@ -63,6 +63,7 @@ class Connection
protected $bind_pw;
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
@@ -109,6 +110,8 @@ class Connection
protected $supports_v3 = false;
protected $supports_tls = false;
+ protected $capabilities;
+
/**
* Constructor
*
@@ -365,6 +368,7 @@ class Connection
$ds = ldap_connect($this->hostname, $this->port);
$cap = $this->discoverCapabilities($ds);
+ $this->capabilities = $cap;
if ($use_tls) {
if ($cap->starttls) {
@@ -399,7 +403,6 @@ class Connection
// TODO: remove this -> FORCING v3 for now
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
-
Logger::warning('No LDAPv3 support detected');
}
@@ -429,6 +432,116 @@ class Connection
}
}
+ protected function hasCapabilityStarTSL($cap)
+ {
+ $cap = $this->getExtensionCapabilities($cap);
+ return isset($cap['1.3.6.1.4.1.1466.20037']);
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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.
+ */
+ 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()
+ {
+ $cap = $this->capabilities;
+ if (!isset($cap->namingContexts)) {
+ return array();
+ }
+ if (!is_array($cap->namingContexts)) {
+ return array($cap->namingContexts);
+ }
+ return $cap->namingContexts;
+ }
+
+ /**
+ * Discover the capabilities of the given ldap-server
+ *
+ * @param $ds The link identifier of the current ldap connection
+ *
+ * @return bool|object The capabilities or false if the server has none
+ * @throws Exception When the capability query fails
+ */
protected function discoverCapabilities($ds)
{
$query = $this->select()->from(
@@ -471,69 +584,49 @@ class Connection
$cap = (object) array(
'ldapv3' => false,
'starttls' => false,
+ 'msCapabilities' => array()
);
if ($entry === false) {
// TODO: Is it OK to have no capabilities?
- return $cap;
+ return false;
}
$ldapAttributes = ldap_get_attributes($ds, $entry);
- $result = $this->cleanupAttributes(
- $ldapAttributes
- );
+ $result = $this->cleanupAttributes($ldapAttributes);
+ $cap->ldapv3 = $this->hasCapabilityLdapV3($result);
+ $cap->starttls = $this->hasCapabilityStarTSL($result);
+ $cap->msCapabilities = $this->getMsCapabilities($result);
+ $cap->namingContexts = $result->namingContexts;
/*
if (isset($result->dnsHostName)) {
ldap_set_option($ds, LDAP_OPT_HOST_NAME, $result->dnsHostName);
}
*/
- if ((is_string($result->supportedLDAPVersion)
- && (int) $result->supportedLDAPVersion === 3)
- || (is_array($result->supportedLDAPVersion)
- && in_array(3, $result->supportedLDAPVersion)
- )) {
- $cap->ldapv3 = true;
- }
-
- if (isset($result->supportedCapabilities)) {
- foreach ($result->supportedCapabilities as $oid) {
- if (array_key_exists($oid, $this->ms_capability)) {
- // echo $this->ms_capability[$oid] . "\n";
- }
- }
- }
- if (isset($result->supportedExtension)) {
- foreach ($result->supportedExtension as $oid) {
- if (array_key_exists($oid, $this->ldap_extension)) {
- if ($this->ldap_extension[$oid] === 'STARTTLS') {
- $cap->starttls = true;
- }
- }
- }
- }
return $cap;
}
- public function connect()
+ public function connect($anonymous = false)
{
if ($this->ds !== null) {
return;
}
$this->ds = $this->prepareNewConnection();
- $r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
-
- if (! $r) {
- throw new \Exception(
- sprintf(
- 'LDAP connection to %s:%s (%s / %s) failed: %s',
- $this->hostname,
- $this->port,
- $this->bind_dn,
- '***' /* $this->bind_pw */,
- ldap_error($this->ds)
- )
- );
+ if (!$anonymous) {
+ $r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
+ if (! $r) {
+ throw new \Exception(
+ sprintf(
+ 'LDAP connection to %s:%s (%s / %s) failed: %s',
+ $this->hostname,
+ $this->port,
+ $this->bind_dn,
+ '***' /* $this->bind_pw */,
+ ldap_error($this->ds)
+ )
+ );
+ }
}
}
From fa797de05fb16fc297ef6e63bf60220b469027d6 Mon Sep 17 00:00:00 2001
From: Johannes Meyer
Date: Wed, 11 Jun 2014 08:58:53 +0200
Subject: [PATCH 06/29] Fix invalid default logging configuration
---
config/config.ini.in | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/config/config.ini.in b/config/config.ini.in
index 35febdb3b..9b3fc8136 100644
--- a/config/config.ini.in
+++ b/config/config.ini.in
@@ -14,7 +14,7 @@ timeFormat = "g:i A"
[logging]
enable = true
; Writing to a Stream
-type = "stream"
+type = "file"
; Write data to the following file
target = "@icingaweb_log_path@/icingaweb.log"
; Write data to a PHP stream
@@ -22,8 +22,8 @@ target = "@icingaweb_log_path@/icingaweb.log"
; Writing to the System Log
;type = "syslog"
-; Prefix all syslog messages generated with the string "Icinga Web"
-;application = "Icinga Web"
+; Prefix all syslog messages generated with the string "icingaweb"
+;application = "icingaweb"
;facility = "LOG_USER"
level = 1
From 159d765f14a2d18e5fd1dcc814ad4079c094bf36 Mon Sep 17 00:00:00 2001
From: Johannes Meyer
Date: Wed, 11 Jun 2014 13:39:22 +0200
Subject: [PATCH 07/29] Fix that calling ActionController::translate() throws
an exception
Translating strings must not throw an exception
even if the given domain is not valid.
fixes #6432
---
.../Application/ApplicationBootstrap.php | 3 +--
library/Icinga/Application/functions.php | 2 +-
library/Icinga/Util/Translator.php | 6 -----
.../Web/Controller/ActionController.php | 2 +-
test/php/regression/Bug6432Test.php | 23 +++++++++++++++++++
5 files changed, 26 insertions(+), 10 deletions(-)
create mode 100644 test/php/regression/Bug6432Test.php
diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php
index e8d407216..7e6fed800 100644
--- a/library/Icinga/Application/ApplicationBootstrap.php
+++ b/library/Icinga/Application/ApplicationBootstrap.php
@@ -30,7 +30,6 @@
namespace Icinga\Application;
-use DateTimeZone;
use Exception;
use Zend_Config;
use Icinga\Application\Modules\Manager as ModuleManager;
@@ -484,7 +483,7 @@ abstract class ApplicationBootstrap
$localeDir = $this->getApplicationDir('locale');
if (file_exists($localeDir) && is_dir($localeDir)) {
- Translator::registerDomain('icinga', $localeDir);
+ Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir);
}
return $this;
diff --git a/library/Icinga/Application/functions.php b/library/Icinga/Application/functions.php
index b600028d6..0530b6af2 100644
--- a/library/Icinga/Application/functions.php
+++ b/library/Icinga/Application/functions.php
@@ -32,7 +32,7 @@ use \Icinga\Util\Translator;
if (extension_loaded('gettext')) {
function t($messageId)
{
- return Translator::translate($messageId, 'icinga');
+ return Translator::translate($messageId, Translator::DEFAULT_DOMAIN);
}
function mt($domain, $messageId)
diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php
index 733806c72..f868b3465 100644
--- a/library/Icinga/Util/Translator.php
+++ b/library/Icinga/Util/Translator.php
@@ -62,15 +62,9 @@ class Translator
* @param string $domain The primary domain to use
*
* @return string The translated string
- *
- * @throws Exception In case the given domain is unknown
*/
public static function translate($text, $domain)
{
- if ($domain !== self::DEFAULT_DOMAIN && !array_key_exists($domain, self::$knownDomains)) {
- throw new Exception("Cannot translate string '$text' with unknown domain '$domain'");
- }
-
$res = dgettext($domain, $text);
if ($res === $text && $domain !== self::DEFAULT_DOMAIN) {
return dgettext(self::DEFAULT_DOMAIN, $text);
diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php
index 870078e96..7593db752 100644
--- a/library/Icinga/Web/Controller/ActionController.php
+++ b/library/Icinga/Web/Controller/ActionController.php
@@ -301,7 +301,7 @@ class ActionController extends Zend_Controller_Action
public function translate($text)
{
$module = $this->getRequest()->getModuleName();
- $domain = $module === 'default' ? 'icinga' : $module;
+ $domain = $module === 'default' ? Translator::DEFAULT_DOMAIN : $module;
return Translator::translate($text, $domain);
}
diff --git a/test/php/regression/Bug6432Test.php b/test/php/regression/Bug6432Test.php
new file mode 100644
index 000000000..4194d9d41
--- /dev/null
+++ b/test/php/regression/Bug6432Test.php
@@ -0,0 +1,23 @@
+assertEquals('test', Translator::translate('test', 'invalid_domain'));
+ }
+}
From bca166c64432f35d2195a85e58fbdb0778e07371 Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Wed, 11 Jun 2014 13:14:05 +0200
Subject: [PATCH 08/29] Do not throw an exception when the username does not
exist
refs #6457
---
.../Authentication/Backend/LdapUserBackend.php | 14 ++++++++------
library/Icinga/Protocol/Ldap/Connection.php | 15 +++++++++------
2 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php
index d17cb3624..44445cec8 100644
--- a/library/Icinga/Authentication/Backend/LdapUserBackend.php
+++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php
@@ -94,16 +94,18 @@ class LdapUserBackend extends UserBackend
* @param User $user
* @param string $password
*
- * @return bool|null
- * @throws AuthenticationException
+ * @return bool True when the authentication was successful, false when the username or password was invalid
+ * @throws AuthenticationException When an error occurred during authentication
*/
public function authenticate(User $user, $password)
{
try {
- return $this->conn->testCredentials(
- $this->conn->fetchDN($this->createQuery($user->getUsername())),
- $password
- );
+ $userDn = $this->conn->fetchDN($this->createQuery($user->getUsername()));
+ if (!$userDn) {
+ // User does not exist
+ return false;
+ }
+ return $this->conn->testCredentials($userDn, $password);
} catch (Exception $e) {
throw new AuthenticationException(
sprintf(
diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php
index 6419fbb6f..2611f5580 100644
--- a/library/Icinga/Protocol/Ldap/Connection.php
+++ b/library/Icinga/Protocol/Ldap/Connection.php
@@ -207,16 +207,19 @@ class Connection
return true;
}
+ /**
+ * Fetch the distinguished name of the first result of the given query
+ *
+ * @param $query
+ * @param array $fields
+ *
+ * @return bool|String Returns the distinguished name, or false when the given query yields no results
+ */
public function fetchDN($query, $fields = array())
{
$rows = $this->fetchAll($query, $fields);
if (count($rows) !== 1) {
- throw new \Exception(
- sprintf(
- 'Cannot fetch single DN for %s',
- $query
- )
- );
+ return false;
}
return key($rows);
}
From 6c82cb8988349b513f862a704b0db5d02c0792a7 Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Wed, 11 Jun 2014 14:22:52 +0200
Subject: [PATCH 09/29] Check ldap backend health during Authentication
Check if authentication is possible during authentication, to generate more
useful error and log messages, in case the backend configuration is wrong
ref #6457
---
.../Backend/LdapUserBackend.php | 57 ++++++++++++++++++-
1 file changed, 55 insertions(+), 2 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php
index 44445cec8..37216fdd5 100644
--- a/library/Icinga/Authentication/Backend/LdapUserBackend.php
+++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php
@@ -75,12 +75,46 @@ class LdapUserBackend extends UserBackend
);
}
+ /**
+ * Probe the backend to test if authentication is possible
+ *
+ * Try to bind to the backend and query all available users to check if:
+ *
+ * - User connection credentials are correct and the bind is possible
+ * - At least one user exists
+ * - The specified userClass has the property specified by userNameAttribute
+ *
+ *
+ * @throws AuthenticationException When authentication is not possible
+ */
+ protected function assertAuthenticationPossible()
+ {
+ $q = $this->conn->select()->from($this->userClass);
+ $result = $q->fetchRow();
+ if (!isset($result)) {
+ throw new AuthenticationException(
+ sprintf('No users with objectClass="%s" in DN="%s" available',
+ $this->userClass,
+ $this->userNameAttribute
+ ));
+ }
+
+ if (!isset($result->{$this->userNameAttribute})) {
+ throw new AuthenticationException(
+ sprintf('UserNameAttribute "%s" not existing in objectClass="%s"',
+ $this->userNameAttribute,
+ $this->userClass
+ ));
+ }
+ }
+
/**
* Test whether the given user exists
*
* @param User $user
*
* @return bool
+ * @throws AuthenticationException
*/
public function hasUser(User $user)
{
@@ -93,12 +127,29 @@ class LdapUserBackend extends UserBackend
*
* @param User $user
* @param string $password
+ * @param boolean $healthCheck Perform additional health checks to generate more useful
+ * exceptions in case of a configuration or backend error
*
* @return bool True when the authentication was successful, false when the username or password was invalid
- * @throws AuthenticationException When an error occurred during authentication
+ * @throws AuthenticationException When an error occurred during authentication and authentication is not possible
*/
- public function authenticate(User $user, $password)
+ public function authenticate(User $user, $password, $healthCheck = true)
{
+ if ($healthCheck) {
+ try {
+ $this->assertAuthenticationPossible();
+ } catch (AuthenticationException $e) {
+ // Authentication not possible
+ throw new AuthenticationException(
+ sprintf(
+ 'Authentication against backend "%s" not possible: ',
+ $this->getName()
+ ),
+ 0,
+ $e
+ );
+ }
+ }
try {
$userDn = $this->conn->fetchDN($this->createQuery($user->getUsername()));
if (!$userDn) {
@@ -107,6 +158,7 @@ class LdapUserBackend extends UserBackend
}
return $this->conn->testCredentials($userDn, $password);
} catch (Exception $e) {
+ // Error during authentication of this specific user
throw new AuthenticationException(
sprintf(
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
@@ -126,6 +178,7 @@ class LdapUserBackend extends UserBackend
*/
public function count()
{
+
return $this->conn->count(
$this->conn->select()->from(
$this->userClass,
From c42c7977bef5f8e2bdb844fa7a7739733e963ad1 Mon Sep 17 00:00:00 2001
From: Matthias Jentsch
Date: Wed, 11 Jun 2014 15:04:19 +0200
Subject: [PATCH 10/29] Call extended backend health checks when creating ldap
authentication backends
fixes #6457
---
application/forms/Config/Authentication/LdapBackendForm.php | 3 +++
library/Icinga/Authentication/Backend/LdapUserBackend.php | 6 +++---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/application/forms/Config/Authentication/LdapBackendForm.php b/application/forms/Config/Authentication/LdapBackendForm.php
index 966cea1cc..051b3c0c0 100644
--- a/application/forms/Config/Authentication/LdapBackendForm.php
+++ b/application/forms/Config/Authentication/LdapBackendForm.php
@@ -179,9 +179,12 @@ class LdapBackendForm extends BaseBackendForm
$backendConfig->user_class,
$backendConfig->user_name_attribute
);
+ $testConn->assertAuthenticationPossible();
+ /*
if ($testConn->count() === 0) {
throw new Exception('No Users Found On Directory Server');
}
+ */
} catch (Exception $exc) {
$this->addErrorMessage(
t('Connection Validation Failed: ' . $exc->getMessage())
diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php
index 37216fdd5..d19437ae6 100644
--- a/library/Icinga/Authentication/Backend/LdapUserBackend.php
+++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php
@@ -87,15 +87,15 @@ class LdapUserBackend extends UserBackend
*
* @throws AuthenticationException When authentication is not possible
*/
- protected function assertAuthenticationPossible()
+ public function assertAuthenticationPossible()
{
$q = $this->conn->select()->from($this->userClass);
$result = $q->fetchRow();
if (!isset($result)) {
throw new AuthenticationException(
- sprintf('No users with objectClass="%s" in DN="%s" available',
+ sprintf('No objects with objectClass="%s" in DN="%s" found.',
$this->userClass,
- $this->userNameAttribute
+ $this->conn->getDN()
));
}
From c09341d77ece175be4b7ed8c3499c15f2d6af101 Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 14:43:27 +0200
Subject: [PATCH 11/29] Autologin: Do NOT sanitize username
I don't know the reason why this was done initially but a username must not be changed.
---
library/Icinga/Authentication/Backend/AutoLoginBackend.php | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 8fd7a7c5c..173dde6a7 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -57,11 +57,7 @@ class AutoLoginBackend extends UserBackend
&& isset($_SERVER['AUTH_TYPE'])
&& in_array($_SERVER['AUTH_TYPE'], array('Basic', 'Digest')) === true
) {
- $username = filter_var(
- $_SERVER['PHP_AUTH_USER'],
- FILTER_SANITIZE_STRING,
- FILTER_FLAG_ENCODE_HIGH|FILTER_FLAG_ENCODE_LOW
- );
+ $username = $_SERVER['PHP_AUTH_USER'];
if ($username !== false) {
if ($this->stripUsernameRegexp !== null) {
From 63fc8eb27eed1b96b874146944bac75838bb8fab Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 14:47:15 +0200
Subject: [PATCH 12/29] Autologin: Use REMOTE_USER for authentication
It's not safe to rely on PHP_AUTH_USER and PHP_AUTH_TYPE because
PHP cgi handlers (fgcid for example) only set the REMOTE_USER environment variable
and the authentication type for negogiation methods (Kerberos for example) is neither
Basic nor Digest.
We may have to add REDIRECT_REMOTE_USER for authentication for proxy setups.
---
library/Icinga/Authentication/Backend/AutoLoginBackend.php | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 173dde6a7..2fdfe1037 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -53,11 +53,8 @@ class AutoLoginBackend extends UserBackend
*/
public function hasUser(User $user)
{
- if (isset($_SERVER['PHP_AUTH_USER'])
- && isset($_SERVER['AUTH_TYPE'])
- && in_array($_SERVER['AUTH_TYPE'], array('Basic', 'Digest')) === true
- ) {
- $username = $_SERVER['PHP_AUTH_USER'];
+ if (isset($_SERVER['REMOTE_USER'])) {
+ $username = $_SERVER['REMOTE_USER'];
if ($username !== false) {
if ($this->stripUsernameRegexp !== null) {
From 7215ba4f595b9136fbd7cd882002c6ed15365832 Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 15:04:15 +0200
Subject: [PATCH 13/29] Autologin: Do not require a bogus password in the
source code
---
application/controllers/AuthenticationController.php | 5 ++---
library/Icinga/Authentication/Backend/AutoLoginBackend.php | 2 +-
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php
index f50987de7..4476ecebd 100644
--- a/application/controllers/AuthenticationController.php
+++ b/application/controllers/AuthenticationController.php
@@ -63,8 +63,6 @@ class AuthenticationController extends ActionController
$this->view->form = new LoginForm();
$this->view->form->setRequest($this->_request);
$this->view->title = $this->translate('Icingaweb Login');
- $user = new User('');
- $password = '';
try {
$redirectUrl = Url::fromPath($this->_request->getParam('redirect', 'dashboard'));
@@ -95,9 +93,10 @@ class AuthenticationController extends ActionController
if ($this->getRequest()->isGet()) {
+ $user = new User('');
foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) {
- $authenticated = $backend->authenticate($user, $password);
+ $authenticated = $backend->authenticate($user);
if ($authenticated === true) {
$auth->setAuthenticated($user);
$this->redirectNow($redirectUrl);
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 2fdfe1037..8cde07878 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -75,7 +75,7 @@ class AutoLoginBackend extends UserBackend
*
* @return bool
*/
- public function authenticate(User $user, $password)
+ public function authenticate(User $user, $password = null)
{
return $this->hasUser($user);
}
From 65a2bd41bce8fa601f6a55d0a0da5b9a00909551 Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 15:09:06 +0200
Subject: [PATCH 14/29] Autologin: Do not use absolute `use'
---
library/Icinga/Authentication/Backend/AutoLoginBackend.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 8cde07878..5a72f475a 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -4,9 +4,9 @@
namespace Icinga\Authentication\Backend;
+use Zend_Config;
use Icinga\Authentication\UserBackend;
use Icinga\User;
-use \Zend_Config;
/**
* Test login with external authentication mechanism, e.g. Apache
From 992ccf4f6dfe3d422474f810cde7b1d18528d185 Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 15:27:36 +0200
Subject: [PATCH 15/29] Autologin: Actually set the username upon
authentication
Before, when using autologin the username of the authenticated user always was the empty string.
---
.../Authentication/Backend/AutoLoginBackend.php | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 5a72f475a..67206b8de 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -55,13 +55,16 @@ class AutoLoginBackend extends UserBackend
{
if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER'];
-
- if ($username !== false) {
- if ($this->stripUsernameRegexp !== null) {
- $username = preg_replace($this->stripUsernameRegexp, '', $username);
+ if ($this->stripUsernameRegexp !== null) {
+ $stripped = preg_replace($this->stripUsernameRegexp, '', $username);
+ if ($stripped !== false) {
+ // TODO(el): PHP issues a warning when PHP cannot compile the regular expression. Should we log an
+ // additional message in that case?
+ $username = $stripped;
}
- return true;
}
+ $user->setUsername($username);
+ return true;
}
return false;
From 7d2ee41f4247ee1340e7d9044399e8af3eb70a29 Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Wed, 11 Jun 2014 15:33:33 +0200
Subject: [PATCH 16/29] Autologin: Fix PHPDoc
---
.../Authentication/Backend/AutoLoginBackend.php | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index 67206b8de..d793b50dd 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -31,13 +31,11 @@ class AutoLoginBackend extends UserBackend
}
/**
- * (PHP 5 >= 5.1.0)
- * Count elements of an object
- * @link http://php.net/manual/en/countable.count.php
- * @return int The custom count as an integer.
- *
- *
- * The return value is cast to an integer.
+ * Count the available users
+ *
+ * Autologin backends will always return 1
+ *
+ * @return int
*/
public function count()
{
@@ -73,8 +71,8 @@ class AutoLoginBackend extends UserBackend
/**
* Authenticate
*
- * @param User $user
- * @param string $password
+ * @param User $user
+ * @param string $password
*
* @return bool
*/
From dfcf3d28e686af99d3685e8a914df003b6ad925d Mon Sep 17 00:00:00 2001
From: Thomas Gelf
Date: Thu, 12 Jun 2014 08:07:04 +0000
Subject: [PATCH 17/29] CSS/pagination: avoid text-selection
Clicking fast through pagination resulted in irritating text
selections and therefore uncomfortable behaviour. Should be
fixed now.
---
public/css/icinga/pagination.less | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/public/css/icinga/pagination.less b/public/css/icinga/pagination.less
index 21336d145..899f34a2f 100644
--- a/public/css/icinga/pagination.less
+++ b/public/css/icinga/pagination.less
@@ -2,6 +2,12 @@ ul.pagination {
font-size: 0.68em;
padding: 0;
display: inline;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
#layout.twocols u.pagination {
From 3047992ab576d4caa58c3f4a5330dceb7f7dd675 Mon Sep 17 00:00:00 2001
From: Thomas Gelf
Date: Thu, 12 Jun 2014 08:20:57 +0000
Subject: [PATCH 18/29] CSS/tables: improve row hover styles
Added a default hover color, fix inheritage and missing states.
---
public/css/icinga/monitoring-colors.less | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/public/css/icinga/monitoring-colors.less b/public/css/icinga/monitoring-colors.less
index d0bc87cc3..0e16a5253 100644
--- a/public/css/icinga/monitoring-colors.less
+++ b/public/css/icinga/monitoring-colors.less
@@ -127,15 +127,12 @@ tr.state.handled td.state, tr.state.ok td.state, tr.state.up td.state, tr.state.
color: black;
background-color: transparent;
}
+
tr[href].active {
background-color: #ddd;
color: black;
}
-tr.state[href]:hover, tr.state[href].active td.state {
-
-}
-
tr.state.ok td.state, tr.state.up td.state {
border-left-color: @colorOk;
}
@@ -195,12 +192,12 @@ tr.state.handled td.state {
/* HOVER colors */
-
-tr.state[href]:hover, tr.state[href]:hover td.state {
+tr[href]:hover, tr.state[href]:hover td.state {
color: white;
+ background-color: #555;
}
-tr.state.ok:hover {
+tr.state.ok[href]:hover, tr.state.up[href]:hover {
background-color: @colorOk;
}
@@ -248,6 +245,11 @@ tr.state.unreachable[href]:hover {
tr.state.unreachable.handled[href]:hover {
background-color: @colorUnreachableHandled;
}
+
+tr.state[href]:hover td.state {
+ background-color: inherit !important;
+}
+
/* END of HOVER colors */
/* END of special tables and states */
From d28d20696c23cec75e63b2a3eec7025e590ecb1e Mon Sep 17 00:00:00 2001
From: Johannes Meyer
Date: Thu, 12 Jun 2014 14:16:28 +0200
Subject: [PATCH 19/29] Move binary testing stuff and phpunit.xml to its own
module
refs #6092
---
{test/php => modules/test}/Makefile | 0
{test/php => modules/test}/bin/README | 0
{test/php => modules/test}/bin/common.h | 0
{test/php => modules/test}/bin/extcmd_test.c | 0
{test/php => modules/test}/bin/shared.h | 0
{test/php => modules/test}/phpunit.xml | 15 +++++++--------
6 files changed, 7 insertions(+), 8 deletions(-)
rename {test/php => modules/test}/Makefile (100%)
rename {test/php => modules/test}/bin/README (100%)
rename {test/php => modules/test}/bin/common.h (100%)
rename {test/php => modules/test}/bin/extcmd_test.c (100%)
rename {test/php => modules/test}/bin/shared.h (100%)
rename {test/php => modules/test}/phpunit.xml (72%)
diff --git a/test/php/Makefile b/modules/test/Makefile
similarity index 100%
rename from test/php/Makefile
rename to modules/test/Makefile
diff --git a/test/php/bin/README b/modules/test/bin/README
similarity index 100%
rename from test/php/bin/README
rename to modules/test/bin/README
diff --git a/test/php/bin/common.h b/modules/test/bin/common.h
similarity index 100%
rename from test/php/bin/common.h
rename to modules/test/bin/common.h
diff --git a/test/php/bin/extcmd_test.c b/modules/test/bin/extcmd_test.c
similarity index 100%
rename from test/php/bin/extcmd_test.c
rename to modules/test/bin/extcmd_test.c
diff --git a/test/php/bin/shared.h b/modules/test/bin/shared.h
similarity index 100%
rename from test/php/bin/shared.h
rename to modules/test/bin/shared.h
diff --git a/test/php/phpunit.xml b/modules/test/phpunit.xml
similarity index 72%
rename from test/php/phpunit.xml
rename to modules/test/phpunit.xml
index b36e81fe8..83fc9d9ac 100644
--- a/test/php/phpunit.xml
+++ b/modules/test/phpunit.xml
@@ -1,25 +1,24 @@
-
+
- application/
- bin/
- library/
+ ../../test/php/application/
+ ../../test/php/library/
- ../../modules/*/test/php
- ../../modules/*/test/php/regression
+ ../*/test/php
+ ../*/test/php/regression
- regression/
- ../../modules/*/test/regression
+ ../../test/php/regression/
+ ../*/test/php/regression
From 32a7decc3e4ee153d177a7a7545d1ee10c95ffa7 Mon Sep 17 00:00:00 2001
From: Johannes Meyer
Date: Thu, 12 Jun 2014 14:16:53 +0200
Subject: [PATCH 20/29] Remove python test-runners
refs #6092
---
test/php/checkswag | 127 ---------------------------------------------
test/php/runtests | 124 -------------------------------------------
2 files changed, 251 deletions(-)
delete mode 100755 test/php/checkswag
delete mode 100755 test/php/runtests
diff --git a/test/php/checkswag b/test/php/checkswag
deleted file mode 100755
index c5abdb5d6..000000000
--- a/test/php/checkswag
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import sys
-import subprocess
-from pipes import quote
-from optparse import OptionParser, BadOptionError, AmbiguousOptionError
-
-
-APPLICATION = 'phpcs'
-DEFAULT_ARGS = ['-p', '--standard=PSR2', '--extensions=php',
- '--encoding=utf-8']
-
-VAGRANT_SCRIPT = '/vagrant/test/php/checkswag'
-REPORT_DIRECTORY = '../../build/log'
-
-
-class PassThroughOptionParser(OptionParser):
- """
- An unknown option pass-through implementation of OptionParser.
-
- When unknown arguments are encountered, bundle with largs and try again,
- until rargs is depleted.
-
- sys.exit(status) will still be called if a known argument is passed
- incorrectly (e.g. missing arguments or bad argument types, etc.)
-
- Borrowed from: http://stackoverflow.com/a/9307174
- """
- def _process_args(self, largs, rargs, values):
- while rargs:
- try:
- OptionParser._process_args(self, largs, rargs, values)
- except (BadOptionError, AmbiguousOptionError), error:
- largs.append(error.opt_str)
-
-
-def execute_command(command, return_output=False, shell=False):
- prog = subprocess.Popen(command, shell=shell,
- stdout=subprocess.PIPE
- if return_output
- else None)
- return prog.wait() if not return_output else \
- prog.communicate()[0]
-
-
-def get_report_directory():
- path = os.path.abspath(REPORT_DIRECTORY)
-
- try:
- os.makedirs(REPORT_DIRECTORY)
- except OSError:
- pass
-
- return path
-
-
-def get_script_directory():
- return os.path.dirname(os.path.abspath(sys.argv[0]))
-
-
-def parse_commandline():
- parser = PassThroughOptionParser(usage='%prog [options] [additional arguments'
- ' for {0}]'.format(APPLICATION))
- parser.add_option('-b', '--build', action='store_true',
- help='Enable reporting.')
- parser.add_option('-v', '--verbose', action='store_true',
- help='Be more verbose.')
- parser.add_option('-i', '--include', metavar='PATTERN', action='append',
- help='Include only specific files/test cases.'
- ' (Can be supplied multiple times.)')
- parser.add_option('-e', '--exclude', metavar='PATTERN', action='append',
- help='Exclude specific files/test cases. '
- '(Can be supplied multiple times.)')
- parser.add_option('-V', '--vagrant', action='store_true',
- help='Run in vagrant VM')
- return parser.parse_args()
-
-
-def main():
- options, arguments = parse_commandline()
-
- if options.vagrant and os.environ['USER'] != 'vagrant':
- # Check if vagrant is installed
- vagrant_path = execute_command('which vagrant', True, True).strip()
- if not vagrant_path:
- print 'ERROR: vagrant not found!'
- return 2
-
- # Call the script in the Vagrant VM with the same parameters
- commandline = ' '.join(quote(p) for p in sys.argv[1:])
- return execute_command('vagrant ssh -c "{0} {1}"'
- ''.format(VAGRANT_SCRIPT, commandline),
- shell=True)
- else:
- # Environment preparation and verification
- os.chdir(get_script_directory())
- application_path = execute_command('which {0}'.format(APPLICATION),
- True, True).strip()
- if not application_path:
- print 'ERROR: {0} not found!'.format(APPLICATION)
- return 2
-
- # Commandline preparation
- command_options = []
- if options.verbose:
- command_options.append('-v')
- if options.build:
- result_path = os.path.join(get_report_directory(),
- 'phpcs_results.xml')
- command_options.append('--report-checkstyle=' + result_path)
- if options.exclude:
- command_options.append('--ignore=' + ','.join(options.exclude))
- if options.include:
- arguments.extend(options.include)
- else:
- arguments.extend(['../../application', '../../bin',
- '../../library/Icinga'])
-
- # Application invocation..
- execute_command([application_path] + DEFAULT_ARGS +
- command_options + arguments)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/test/php/runtests b/test/php/runtests
deleted file mode 100755
index 13f53db1f..000000000
--- a/test/php/runtests
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import sys
-import subprocess
-from pipes import quote
-from fnmatch import fnmatch
-from optparse import OptionParser, BadOptionError, AmbiguousOptionError
-
-
-APPLICATION = 'phpunit'
-DEFAULT_ARGS = []
-
-VAGRANT_SCRIPT = '/vagrant/test/php/runtests'
-REPORT_DIRECTORY = '../../build/log'
-
-
-class PassThroughOptionParser(OptionParser):
- """
- An unknown option pass-through implementation of OptionParser.
-
- When unknown arguments are encountered, bundle with largs and try again,
- until rargs is depleted.
-
- sys.exit(status) will still be called if a known argument is passed
- incorrectly (e.g. missing arguments or bad argument types, etc.)
-
- Borrowed from: http://stackoverflow.com/a/9307174
- """
- def _process_args(self, largs, rargs, values):
- while rargs:
- try:
- OptionParser._process_args(self, largs, rargs, values)
- except (BadOptionError, AmbiguousOptionError), error:
- largs.append(error.opt_str)
-
-
-def execute_command(command, return_output=False, shell=False):
- prog = subprocess.Popen(command, shell=shell,
- stdout=subprocess.PIPE
- if return_output
- else None)
- return prog.wait() if not return_output else \
- prog.communicate()[0]
-
-
-def get_report_directory():
- path = os.path.abspath(REPORT_DIRECTORY)
-
- try:
- os.makedirs(REPORT_DIRECTORY)
- except OSError:
- pass
-
- return path
-
-
-def get_script_directory():
- return os.path.dirname(os.path.abspath(sys.argv[0]))
-
-
-def parse_commandline():
- parser = PassThroughOptionParser(usage='%prog [options] [additional arguments'
- ' for {0}]'.format(APPLICATION))
- parser.add_option('-b', '--build', action='store_true',
- help='Enable reporting.')
- parser.add_option('-v', '--verbose', action='store_true',
- help='Be more verbose.')
- parser.add_option('-i', '--include', metavar='PATTERN',
- help='Include only specific files/test cases.')
- parser.add_option('-V', '--vagrant', action='store_true',
- help='Run in vagrant VM')
- return parser.parse_args()
-
-
-def main():
- options, arguments = parse_commandline()
-
- if options.vagrant and os.environ['USER'] != 'vagrant':
- # Check if vagrant is installed
- vagrant_path = execute_command('which vagrant', True, True).strip()
- if not vagrant_path:
- print 'ERROR: vagrant not found!'
- return 2
-
- # Call the script in the Vagrant VM with the same parameters
- commandline = ' '.join(quote(p) for p in sys.argv[1:])
- return execute_command('vagrant ssh -c "{0} {1}"'
- ''.format(VAGRANT_SCRIPT, commandline),
- shell=True)
- else:
- # Environment preparation and verification
- os.chdir(get_script_directory())
- application_path = execute_command('which {0}'.format(APPLICATION),
- True, True).strip()
- if not application_path:
- print 'ERROR: {0} not found!'.format(APPLICATION)
- return 2
- if not os.path.isfile('./bin/extcmd_test'):
- execute_command('make', shell=True)
-
- # Commandline preparation
- command_options = []
- if options.verbose:
- command_options.append('--verbose')
- if options.build:
- report_directory = get_report_directory()
- command_options.append('--log-junit')
- command_options.append(os.path.join(report_directory,
- 'phpunit_results.xml'))
- command_options.append('--coverage-html')
- command_options.append(os.path.join(report_directory,
- 'php_html_coverage'))
- if options.include:
- command_options.append('--filter')
- command_options.append(options.include)
-
- # Application invocation..
- execute_command([application_path] + DEFAULT_ARGS +
- command_options + arguments)
- return 0
-
-if __name__ == '__main__':
- sys.exit(main())
From 0805d73e34d3692ffb77a74c31bd04fbdb67ba27 Mon Sep 17 00:00:00 2001
From: Johannes Meyer
Date: Thu, 12 Jun 2014 16:07:25 +0200
Subject: [PATCH 21/29] Add clicommands to run unit- and style-tests
refs #6092
---
library/Icinga/Cli/Params.php | 24 +-
library/Icinga/Util/Process.php | 205 ++++++++++++++++++
.../application/clicommands/PhpCommand.php | 163 ++++++++++++++
3 files changed, 390 insertions(+), 2 deletions(-)
create mode 100644 library/Icinga/Util/Process.php
create mode 100644 modules/test/application/clicommands/PhpCommand.php
diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php
index 1fc47a150..63ae06ea7 100644
--- a/library/Icinga/Cli/Params.php
+++ b/library/Icinga/Cli/Params.php
@@ -10,12 +10,20 @@ class Params
public function __construct($argv)
{
+ $noOptionFlag = false;
$this->program = array_shift($argv);
for ($i = 0; $i < count($argv); $i++) {
- if (substr($argv[$i], 0, 2) === '--') {
+ if ($argv[$i] === '--') {
+ $noOptionFlag = true;
+ } elseif (!$noOptionFlag && substr($argv[$i], 0, 2) === '--') {
$key = substr($argv[$i], 2);
if (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') {
$this->params[$key] = true;
+ } elseif (array_key_exists($key, $this->params)) {
+ if (!is_array($this->params[$key])) {
+ $this->params[$key] = array($this->params[$key]);
+ }
+ $this->params[$key][] = $argv[++$i];
} else {
$this->params[$key] = $argv[++$i];
}
@@ -43,6 +51,11 @@ class Params
return $this->params;
}
+ public function getAllStandalone()
+ {
+ return $this->standalone;
+ }
+
public function __get($key)
{
return $this->get($key);
@@ -95,7 +108,14 @@ class Params
return $default;
}
$result = $this->get($key, $default);
- $this->remove($key);
+ if (is_array($result) && !is_array($default)) {
+ $result = array_shift($result) || $default;
+ if ($result === $default) {
+ $this->remove($key);
+ }
+ } else {
+ $this->remove($key);
+ }
return $result;
}
diff --git a/library/Icinga/Util/Process.php b/library/Icinga/Util/Process.php
new file mode 100644
index 000000000..b3d2b9fe4
--- /dev/null
+++ b/library/Icinga/Util/Process.php
@@ -0,0 +1,205 @@
+resource = proc_open(
+ $cmd,
+ $descriptorSpec,
+ $this->pipes,
+ $cwd,
+ $env
+ );
+
+ if (!is_resource($this->resource)) {
+ throw new RuntimeException("Cannot start process: $cmd");
+ }
+ }
+
+ /**
+ * Start and return a new process
+ *
+ * @param string $cmd The command to start the process
+ * @param string $cwd The working directory of the new process (Must be an absolute path or null)
+ * @param string $stdout A filedescriptor, "pipe" or a filepath
+ * @param string $stderr A filedescriptor, "pipe" or a filepath
+ * @param string $stdin A filedescriptor, "pipe" or a filepath
+ * @param array $env The environment variables (Must be an array or null)
+ *
+ * @return Process
+ *
+ * @throws RuntimeException When the process could not be started
+ */
+ public static function start($cmd, $cwd = null, $stdout = null, $stderr = null, $stdin = null, $env = array())
+ {
+ return new static($cmd, $cwd, $stdout, $stderr, $stdin, $env);
+ }
+
+ /**
+ * Interact with process
+ *
+ * Send data to stdin. Read data from stdout and stderr, until end-of-file is reached.
+ * Wait for process to terminate. The optional input argument should be a string to be
+ * sent to the child process, or null, if no data should be sent to the child.
+ *
+ * Note that you need to pass the equivalent pipes to the constructor for this to work.
+ *
+ * @param string $input Data to send to the child.
+ *
+ * @return array The data from stdout and stderr.
+ */
+ public function communicate($input = null)
+ {
+ if (!isset($this->pipes[1]) && !isset($this->pipes[2])) {
+ $this->wait();
+ return array();
+ }
+
+ $read = $write = array();
+ if (isset($this->pipes[0])) {
+ $write[] = $this->pipes[0];
+ }
+ if (isset($this->pipes[1])) {
+ $read[] = $this->pipes[1];
+ stream_set_blocking($this->pipes[1], 0);
+ }
+ if (isset($this->pipes[2])) {
+ $read[] = $this->pipes[2];
+ stream_set_blocking($this->pipes[2], 0);
+ }
+
+ $stdout = $stderr = '';
+ $readToWatch = $read;
+ $writeToWatch = $write;
+ $exceptToWatch = array();
+ while (stream_select($readToWatch, $writeToWatch, $exceptToWatch, 0, 20000) !== false) {
+ if (!empty($writeToWatch) && $input) {
+ $input = substr($input, fwrite($writeToWatch[0], $input));
+ }
+ foreach ($readToWatch as $pipe) {
+ if (isset($this->pipes[1]) && $pipe === $this->pipes[1]) {
+ $stdout .= stream_get_contents($pipe);
+ } elseif (isset($this->pipes[2]) && $pipe === $this->pipes[2]) {
+ $stderr .= stream_get_contents($pipe);
+ }
+ }
+
+ $readToWatch = array_filter($read, function ($h) { return !feof($h); });
+ $writeToWatch = $input ? $write : array();
+ if (empty($readToWatch) && empty($writeToWatch)) {
+ break;
+ }
+ }
+
+ $this->wait(); // To ensure the process is actually stopped when calling cleanUp() we utilize wait()
+ return array($stdout, $stderr);
+ }
+
+ /**
+ * Return whether the process is still alive and set the returncode
+ *
+ * @return bool
+ */
+ public function poll()
+ {
+ if ($this->resource !== null) {
+ $info = @proc_get_status($this->resource);
+ if ($info !== false) {
+ if ($info['running']) {
+ return true;
+ } elseif ($info['exitcode'] !== -1) {
+ $this->returnCode = $info['exitcode'];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Wait for process to terminate and return its returncode
+ *
+ * @return int
+ */
+ public function wait()
+ {
+ if ($this->returnCode === null && $this->resource !== null) {
+ while ($this->poll()) {
+ usleep(500000);
+ }
+ $this->cleanUp();
+ }
+
+ return $this->returnCode;
+ }
+
+ /**
+ * Cleanup the process resource and its associated pipes
+ */
+ protected function cleanUp()
+ {
+ foreach ($this->pipes as $pipe) {
+ if (is_resource($pipe)) {
+ fclose($pipe);
+ }
+ }
+
+ proc_close($this->resource);
+ $this->resource = null;
+ }
+}
diff --git a/modules/test/application/clicommands/PhpCommand.php b/modules/test/application/clicommands/PhpCommand.php
new file mode 100644
index 000000000..f5a4b49fe
--- /dev/null
+++ b/modules/test/application/clicommands/PhpCommand.php
@@ -0,0 +1,163 @@
+params->shift('build');
+ $include = $this->params->shift('include');
+
+ $phpUnit = exec('which phpunit');
+ if (!file_exists($phpUnit)) {
+ $this->fail('PHPUnit not found. Please install PHPUnit to be able to run the unit-test suites.');
+ }
+
+ $options = array();
+ if ($this->isVerbose) {
+ $options[] = '--verbose';
+ }
+ if ($build) {
+ $reportPath = $this->setupAndReturnReportDirectory();
+ echo $reportPath;
+ $options[] = '--log-junit';
+ $options[] = $reportPath . '/phpunit_results.xml';
+ $options[] = '--coverage-html';
+ $options[] = $reportPath . '/php_html_coverage';
+ }
+ if ($include !== null) {
+ $options[] = '--filter';
+ $options[] = $include;
+ }
+
+ Process::start(
+ $phpUnit . ' ' . join(' ', array_merge($options, $this->params->getAllStandalone())),
+ realpath(__DIR__ . '/../..')
+ )->wait();
+ }
+
+ /**
+ * Run code-style checks
+ *
+ * This command checks whether icingaweb and installed modules match the PSR-2 coding standard.
+ *
+ * USAGE
+ *
+ * icingacli test php style [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Be more verbose.
+ * --build Enable reporting.
+ * --include Include only specific files. (Can be supplied multiple times.)
+ * --exclude Pattern to use for excluding files. (Can be supplied multiple times.)
+ *
+ * EXAMPLES
+ *
+ * icingacli test php style --verbose
+ * icingacli test php style --build
+ * icingacli test php style --include path/to/your/file
+ * icingacli test php style --exclude *someFile* --exclude someOtherFile*
+ */
+ public function styleAction()
+ {
+ $build = $this->params->shift('build');
+ $include = (array) $this->params->shift('include', array());
+ $exclude = (array) $this->params->shift('exclude', array());
+
+ $phpcs = exec('which phpcs');
+ if (!file_exists($phpcs)) {
+ $this->fail(
+ 'PHP_CodeSniffer not found. Please install PHP_CodeSniffer to be able to run code style tests.'
+ );
+ }
+
+ $options = array();
+ if ($this->isVerbose) {
+ $options[] = '-v';
+ }
+ if ($build) {
+ $options[] = '--report-checkstyle=' . $this->setupAndReturnReportDirectory();
+ }
+ if (!empty($exclude)) {
+ $options[] = '--ignore=' . join(',', $exclude);
+ }
+ $arguments = array_filter(array_map(function ($p) { return realpath($p); }, $include));
+ if (empty($arguments)) {
+ $arguments = array(
+ realpath(__DIR__ . '/../../../../application'),
+ realpath(__DIR__ . '/../../../../library/Icinga')
+ );
+ }
+
+ Process::start(
+ $phpcs . ' ' . join(
+ ' ',
+ array_merge(
+ $options,
+ $this->phpcsDefaultParams,
+ $arguments,
+ $this->params->getAllStandalone()
+ )
+ ),
+ realpath(__DIR__ . '/../..')
+ )->wait();
+ }
+
+ /**
+ * Setup the directory where to put report files and return its path
+ *
+ * @return string
+ */
+ protected function setupAndReturnReportDirectory()
+ {
+ $path = realpath(__DIR__ . '/../../../..') . '/build/log';
+ if (!file_exists($path) && !@mkdir($path, 0755, true)) {
+ $this->fail("Could not create directory: $path");
+ }
+
+ return $path;
+ }
+}
From db73d324deda52ee04dd442fbde73f6abc1839db Mon Sep 17 00:00:00 2001
From: Eric Lippmann
Date: Thu, 12 Jun 2014 17:05:54 +0200
Subject: [PATCH 22/29] Autologin: Fix that the backend name must have been
`autologin'
Before, the code validated the name of the backend instead of the `backend' directive against `autologin'.
---
library/Icinga/Authentication/UserBackend.php | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/library/Icinga/Authentication/UserBackend.php b/library/Icinga/Authentication/UserBackend.php
index 70070aa2f..9dcf9c641 100644
--- a/library/Icinga/Authentication/UserBackend.php
+++ b/library/Icinga/Authentication/UserBackend.php
@@ -85,7 +85,14 @@ abstract class UserBackend implements Countable
}
return new $backendConfig->class($backendConfig);
}
- if ($name === 'autologin') {
+ if (($backendType = $backendConfig->backend) === null) {
+ throw new ConfigurationError(
+ 'Authentication configuration for backend "' . $name
+ . '" is missing the backend directive'
+ );
+ }
+ $backendType = strtolower($backendType);
+ if ($backendType === 'autologin') {
$backend = new AutoLoginBackend($backendConfig);
$backend->setName($name);
return $backend;
@@ -96,12 +103,6 @@ abstract class UserBackend implements Countable
. '" is missing the resource directive'
);
}
- if (($backendType = $backendConfig->backend) === null) {
- throw new ConfigurationError(
- 'Authentication configuration for backend "' . $name
- . '" is missing the backend directive'
- );
- }
try {
$resourceConfig = ResourceFactory::getResourceConfig($backendConfig->resource);
} catch (ProgrammingError $e) {
@@ -110,7 +111,7 @@ abstract class UserBackend implements Countable
);
}
$resource = ResourceFactory::createResource($resourceConfig);
- switch (strtolower($backendType)) {
+ switch ($backendType) {
case 'db':
$backend = new DbUserBackend($resource);
break;
From c3eae116242d7040301088606efb9af34285d04c Mon Sep 17 00:00:00 2001
From: Thomas Gelf
Date: Thu, 12 Jun 2014 17:22:17 +0000
Subject: [PATCH 23/29] JS/IE8: fix IE8 error caused by focus()
No more error when using the jQuery wrapper. Focus handling is pretty
outdated, needs special care as soon as we have auto-refreshing search
fields.
---
public/js/icinga/loader.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js
index 918b890d8..96e7b5a92 100644
--- a/public/js/icinga/loader.js
+++ b/public/js/icinga/loader.js
@@ -612,7 +612,7 @@
$container.scrollTop(scrollPos);
}
if (origFocus) {
- origFocus.focus();
+ $(origFocus).focus();
}
// TODO: this.icinga.events.refreshContainer(container);
From 0d15f24f05ce3eab26b8b5b55d11335a41a200b8 Mon Sep 17 00:00:00 2001
From: Thomas Gelf
Date: Thu, 12 Jun 2014 17:25:48 +0000
Subject: [PATCH 24/29] JS/IE: remove unused conditional comments
There is no IE7 support and no special handling for IE9, therefore
I removed unused conditional comments. Left IE8 there as it might ask
for special CSS - however it doesn't right now.
Also added missing iframe-class for Iframes on IE8.
---
application/layouts/scripts/layout.phtml | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml
index 16a245a15..602a0f0c7 100644
--- a/application/layouts/scripts/layout.phtml
+++ b/application/layouts/scripts/layout.phtml
@@ -11,16 +11,13 @@ if (array_key_exists('_dev', $_GET)) {
}
$isIframe = isset($_GET['_render']) && $_GET['_render'] === 'iframe';
+$iframeClass = $isIframe ? ' iframe' : '';
?>
-
-
+
-
+
From 44a7aa6adbace02a74f13ba38e94185c28ce2028 Mon Sep 17 00:00:00 2001
From: Thomas Gelf
Date: Thu, 12 Jun 2014 17:33:28 +0000
Subject: [PATCH 25/29] JS/IE8: deliver legacy jQuery for IE8
This patch makes IcingaWeb deliver a legacy jQuery version for IE8
as it is no longer supported in jQuery 2.x. JS for IE8 will not be
delivered minified to ease troubleshooting on that buggy platform.
fixes #5866
refs #6417
---
application/layouts/scripts/layout.phtml | 7 +++++++
library/Icinga/Application/webrouter.php | 5 +++++
library/Icinga/Web/JavaScript.php | 12 +++++++++++-
3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml
index 602a0f0c7..71b233610 100644
--- a/application/layouts/scripts/layout.phtml
+++ b/application/layouts/scripts/layout.phtml
@@ -10,6 +10,8 @@ if (array_key_exists('_dev', $_GET)) {
$cssfile = 'css/icinga.min.css';
}
+$ie8jsfile = 'js/icinga.ie8.js';
+
$isIframe = isset($_GET['_render']) && $_GET['_render'] === 'iframe';
$iframeClass = $isIframe ? ' iframe' : '';
@@ -47,7 +49,12 @@ $iframeClass = $isIframe ? ' iframe' : '';
= $this->render('body.phtml') ?>
+
+
+