Merge branch 'master' into feature/display-documentation-4820

This commit is contained in:
Eric Lippmann 2014-06-13 17:29:41 +02:00
commit 25a73ea3a1
32 changed files with 736 additions and 439 deletions

View File

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

View File

@ -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())

View File

@ -10,17 +10,16 @@ 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' : '';
?><!DOCTYPE html>
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9"> <![endif]-->
<html class="no-js ie8<?= $iframeClass ?>"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js<?= $isIframe ? ' iframe' : '' ?>"> <!--<![endif]-->
<html class="no-js<?= $iframeClass ?>"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
@ -50,7 +49,12 @@ $isIframe = isset($_GET['_render']) && $_GET['_render'] === 'iframe';
<div id="layout" class="default-layout">
<?= $this->render('body.phtml') ?>
</div>
<!--[if IE 8]>
<script type="text/javascript" src="<?= $this->href($ie8jsfile) ?>"></script>
<![endif]-->
<!--[if gt IE 8]><!-->
<script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script>
<!--<![endif]-->
<script type="text/javascript">
var icinga = new Icinga({
baseUrl: '<?= $this->href('/') ?>'

View File

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

View File

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

View File

@ -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)

View File

@ -47,6 +47,7 @@ $special = array(
'css/icinga.css',
'css/icinga.min.css',
'js/icinga.dev.js',
'js/icinga.ie8.js',
'js/icinga.min.js'
);
@ -72,6 +73,10 @@ if (in_array($path, $special)) {
JavaScript::sendMinified();
break;
case 'js/icinga.ie8.js':
JavaScript::sendForIe8();
break;
default:
return false;
}

View File

@ -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
@ -31,13 +31,11 @@ class AutoLoginBackend extends UserBackend
}
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* Count the available users
*
* Autologin backends will always return 1
*
* @return int
*/
public function count()
{
@ -53,22 +51,18 @@ 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 = filter_var(
$_SERVER['PHP_AUTH_USER'],
FILTER_SANITIZE_STRING,
FILTER_FLAG_ENCODE_HIGH|FILTER_FLAG_ENCODE_LOW
);
if ($username !== false) {
if ($this->stripUsernameRegexp !== null) {
$username = preg_replace($this->stripUsernameRegexp, '', $username);
if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER'];
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;
@ -77,12 +71,12 @@ class AutoLoginBackend extends UserBackend
/**
* Authenticate
*
* @param User $user
* @param string $password
* @param User $user
* @param string $password
*
* @return bool
*/
public function authenticate(User $user, $password)
public function authenticate(User $user, $password = null)
{
return $this->hasUser($user);
}

View File

@ -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:
* <ul>
* <li>User connection credentials are correct and the bind is possible</li>
* <li>At least one user exists</li>
* <li>The specified userClass has the property specified by userNameAttribute</li>
* </ul>
*
* @throws AuthenticationException When authentication is not possible
*/
public function assertAuthenticationPossible()
{
$q = $this->conn->select()->from($this->userClass);
$result = $q->fetchRow();
if (!isset($result)) {
throw new AuthenticationException(
sprintf('No objects with objectClass="%s" in DN="%s" found.',
$this->userClass,
$this->conn->getDN()
));
}
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,18 +127,38 @@ 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|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 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 {
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) {
// Error during authentication of this specific user
throw new AuthenticationException(
sprintf(
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
@ -124,6 +178,7 @@ class LdapUserBackend extends UserBackend
*/
public function count()
{
return $this->conn->count(
$this->conn->select()->from(
$this->userClass,

View File

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

View File

@ -1,21 +1,58 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Cli;
/**
* Params
*
* A class to ease commandline-option and -argument handling.
*/
class Params
{
/**
* The name and path of the executable
*
* @var string
*/
protected $program;
/**
* The arguments
*
* @var array
*/
protected $standalone = array();
/**
* The options
*
* @var array
*/
protected $params = array();
/**
* Parse the given commandline and create a new Params object
*
* @param array $argv The commandline
*/
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];
}
@ -25,6 +62,14 @@ class Params
}
}
/**
* Return the value for an argument by position
*
* @param int $pos The position of the argument
* @param mixed $default The default value to return
*
* @return mixed
*/
public function getStandalone($pos = 0, $default = null)
{
if (isset($this->standalone[$pos])) {
@ -33,26 +78,64 @@ class Params
return $default;
}
/**
* Count and return the number of arguments and options
*
* @return int
*/
public function count()
{
return count($this->standalone) + count($this->params);
}
/**
* Return the options
*
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Return the arguments
*
* @return array
*/
public function getAllStandalone()
{
return $this->standalone;
}
/**
* @see Params::get()
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Return whether the given option exists
*
* @param string $key The option name to check
*
* @return bool
*/
public function has($key)
{
return array_key_exists($key, $this->params);
}
/**
* Return the value of the given option
*
* @param string $key The option name
* @param mixed $default The default value to return
*
* @return mixed
*/
public function get($key, $default = null)
{
if ($this->has($key)) {
@ -61,12 +144,27 @@ class Params
return $default;
}
/**
* Set a value for the given option
*
* @param string $key The option name
* @param mixed $value The value to set
*
* @return self
*/
public function set($key, $value)
{
$this->params[$key] = $value;
return $this;
}
/**
* Remove a single option or multiple options
*
* @param string|array $keys The option or options to remove
*
* @return self
*/
public function remove($keys = array())
{
if (! is_array($keys)) {
@ -80,12 +178,30 @@ class Params
return $this;
}
/**
* Return a copy of this object with the given options being removed
*
* @param string|array $keys The option or options to remove
*
* @return Params
*/
public function without($keys = array())
{
$params = clone($this);
return $params->remove($keys);
}
/**
* Remove and return the value of the given option
*
* Called multiple times for an option with multiple values returns
* them one by one in case the default is not an array.
*
* @param string $key The option name
* @param mixed $default The default value to return
*
* @return mixed
*/
public function shift($key = null, $default = null)
{
if ($key === null) {
@ -95,16 +211,37 @@ 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;
}
/**
* Put the given value onto the argument stack
*
* @param mixed $key The argument
*
* @return self
*/
public function unshift($key)
{
array_unshift($this->standalone, $key);
return $this;
}
/**
* Parse the given commandline
*
* @param array $argv The commandline to parse
*
* @return Params
*/
public static function parse($argv = null)
{
if ($argv === null) {

View File

@ -0,0 +1,114 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{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;
}
}

View File

@ -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
*
@ -204,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);
}
@ -353,12 +359,6 @@ class Connection
return $dir;
}
protected function discoverServerlistForDomain($domain)
{
$ldaps_records = dns_get_record('_ldaps._tcp.' . $domain, DNS_SRV);
$ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
}
protected function prepareNewConnection()
{
$use_tls = false;
@ -371,6 +371,7 @@ class Connection
$ds = ldap_connect($this->hostname, $this->port);
$cap = $this->discoverCapabilities($ds);
$this->capabilities = $cap;
if ($use_tls) {
if ($cap->starttls) {
@ -405,7 +406,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');
}
@ -435,6 +435,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(
@ -477,69 +587,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)
)
);
}
}
}

View File

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

View File

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

View File

@ -22,11 +22,15 @@ class JavaScript
);
protected static $vendorFiles = array(
// 'js/vendor/jquery-1.11.0',
'js/vendor/jquery-2.1.0',
'js/vendor/jquery.sparkline'
);
protected static $ie8VendorFiles = array(
'js/vendor/jquery-1.11.0',
'js/vendor/jquery.sparkline'
);
public static function listModuleFiles()
{
$list = array();
@ -43,6 +47,12 @@ class JavaScript
return self::send(true);
}
public static function sendForIe8()
{
self::$vendorFiles = self::$ie8VendorFiles;
return self::send();
}
public static function send($minified = false)
{
header('Content-Type: application/javascript');

View File

@ -50,10 +50,14 @@ class InlinePie extends AbstractWidget
* @var string
*/
private $template =<<<'EOD'
<span class="sparkline" sparkTitle="{title}" sparkWidth="{width}" sparkHeight="{height}" style="{style}"
sparkSliceColors="[{colors}]" values="{data}" sparkType="pie"></span>
<noscript>
<img class="inlinepie"
title="{title}" src="{url}" style="width: {width}px; height: {height}px; {style}"
data-icinga-colors="{colors}" data-icinga-values="{data}"
/>
</noscript>
EOD;
/**

View File

@ -0,0 +1,160 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Test\Clicommands;
use Icinga\Cli\Command;
/**
* PHP unit- & style-tests
*/
class PhpCommand extends Command
{
/**
* Default arguments and options for PHP_CodeSniffer
*
* @var array
*/
protected $phpcsDefaultParams = array(
'-p',
'--standard=PSR2',
'--extensions=php',
'--encoding=utf-8'
);
/**
* Run all unit-test suites
*
* This command runs the unit- and regression-tests of icingaweb and installed modules.
*
* USAGE
*
* icingacli test php unit [options]
*
* OPTIONS
*
* --verbose Be more verbose.
* --build Enable reporting.
* --include Pattern to use for including files/test cases.
*
* EXAMPLES
*
* icingacli test php unit --verbose
* icingacli test php unit --build
* icingacli test php unit --include *SpecialTest
*/
public function unitAction()
{
$build = $this->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;
}
chdir(realpath(__DIR__ . '/../..'));
passthru($phpUnit . ' ' . join(' ', array_merge($options, $this->params->getAllStandalone())));
}
/**
* 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')
);
}
chdir(realpath(__DIR__ . '/../..'));
passthru(
$phpcs . ' ' . join(
' ',
array_merge(
$options,
$this->phpcsDefaultParams,
$arguments,
$this->params->getAllStandalone()
)
)
);
}
/**
* 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;
}
}

View File

@ -1,25 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit bootstrap="bootstrap.php">
<phpunit bootstrap="../../test/php/bootstrap.php">
<testsuites>
<!--
Unit testing
-->
<testsuite name="unit">
<directory>application/</directory>
<directory>bin/</directory>
<directory>library/</directory>
<directory>../../test/php/application/</directory>
<directory>../../test/php/library/</directory>
<!-- Module tests are independent from core tests -->
<directory>../../modules/*/test/php</directory>
<exclude>../../modules/*/test/php/regression</exclude>
<directory>../*/test/php</directory>
<exclude>../*/test/php/regression</exclude>
</testsuite>
<!--
Regression testing
-->
<testsuite name="regression">
<directory>regression/</directory>
<directory>../../modules/*/test/regression</directory>
<directory>../../test/php/regression/</directory>
<directory>../*/test/php/regression</directory>
</testsuite>
</testsuites>
<filter>

View File

@ -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 */

View File

@ -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 {

View File

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

View File

@ -269,7 +269,7 @@
if (this.processRedirectHeader(req)) return;
// div helps getting an XML tree
var $resp = $('<div>' + icinga.ui.removeImageSourceFromSparklines(req.responseText) + '</div>');
var $resp = $('<div>' + req.responseText + '</div>');
var active = false;
var rendered = false;
var classes;
@ -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) {
@ -617,7 +612,7 @@
$container.scrollTop(scrollPos);
}
if (origFocus) {
origFocus.focus();
$(origFocus).focus();
}
// TODO: this.icinga.events.refreshContainer(container);

View File

@ -631,48 +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(
'<span sparkTitle="' + title +
'" sparkWidth="' + width +
'" sparkHeight="' + height +
'" sparkType="pie" sparkSliceColors="[' +
colors + ']" values="' +
values + '" class="sparkline"></span>'
);
},
/**
* 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;

View File

@ -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())

View File

@ -0,0 +1,23 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Regression;
use Icinga\Test\BaseTestCase;
use Icinga\Util\Translator;
/**
* Regression-Test for bug #6432
*
* Translating strings must not throw an exception even if the given domain is not valid.
*
* @see https://dev.icinga.org/issues/6432
*/
class Bug6432Test extends BaseTestCase
{
public function testWhetherTranslateReturnsTheInputStringInCaseTheGivenDomainIsNotValid()
{
$this->assertEquals('test', Translator::translate('test', 'invalid_domain'));
}
}

View File

@ -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())