Merge branch 'master' into bugfix/improve-form-notifications-8983

Conflicts:
	library/Icinga/Web/Form.php
	modules/setup/application/forms/AdminAccountPage.php
This commit is contained in:
Johannes Meyer 2015-07-20 15:52:10 +02:00
commit 47fbc24f17
77 changed files with 2079 additions and 1349 deletions

View File

@ -53,7 +53,7 @@ class ConfigController extends Controller
));
$tabs->add('usergroupbackend', array(
'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'),
'label' => $this->translate('Usergroup Backends'),
'label' => $this->translate('User Group Backends'),
'url' => 'usergroupbackend/list'
));
return $tabs;

View File

@ -6,6 +6,7 @@ use Icinga\Web\Url;
use Icinga\Web\Widget\Tab;
use Icinga\Application\Config;
use Icinga\Forms\PreferenceForm;
use Icinga\Data\ConfigObject;
use Icinga\User\Preferences\PreferencesStore;
/**
@ -38,13 +39,16 @@ class PreferenceController extends BasePreferenceController
*/
public function indexAction()
{
$storeConfig = Config::app()->getSection('preferences');
$config = Config::app()->getSection('global');
$user = $this->getRequest()->getUser();
$form = new PreferenceForm();
$form->setPreferences($user->getPreferences());
if ($storeConfig->get('store', 'ini') !== 'none') {
$form->setStore(PreferencesStore::create($storeConfig, $user));
if ($config->get('config_backend', 'ini') !== 'none') {
$form->setStore(PreferencesStore::create(new ConfigObject(array(
'store' => $config->get('config_backend', 'ini'),
'resource' => $config->config_resource
)), $user));
}
$form->handleRequest();

View File

@ -160,7 +160,7 @@ class UsergroupbackendController extends Controller
));
$tabs->add('usergroupbackend', array(
'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'),
'label' => $this->translate('Usergroup Backends'),
'label' => $this->translate('User Group Backends'),
'url' => 'usergroupbackend/list'
));
return $tabs;

View File

@ -7,7 +7,6 @@ use Icinga\Application\Icinga;
use Icinga\Data\ResourceFactory;
use Icinga\Web\Form;
/**
* Form class to modify the general application configuration
*/
@ -43,7 +42,7 @@ class ApplicationConfigForm extends Form
$this->addElement(
'select',
'preferences_store',
'global_config_backend',
array(
'required' => true,
'autosubmit' => true,
@ -55,7 +54,7 @@ class ApplicationConfigForm extends Form
)
)
);
if (isset($formData['preferences_store']) && $formData['preferences_store'] === 'db') {
if (isset($formData['global_config_backend']) && $formData['global_config_backend'] === 'db') {
$backends = array();
foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) {
if ($resource['type'] === 'db') {
@ -65,7 +64,7 @@ class ApplicationConfigForm extends Form
$this->addElement(
'select',
'preferences_resource',
'global_config_resource',
array(
'required' => true,
'multiOptions' => $backends,

View File

@ -129,16 +129,13 @@ class DbResourceForm extends Form
*/
public static function isValidResource(Form $form)
{
try {
$resource = ResourceFactory::createResource(new ConfigObject($form->getValues()));
$resource->getConnection()->getConnection();
} catch (Exception $e) {
$form->addError(
$form->translate('Connectivity validation failed, connection to the given resource not possible.')
);
return false;
$result = ResourceFactory::createResource(new ConfigObject($form->getValues()))->inspect();
if ($result->hasError()) {
$form->addError(sprintf($form->translate('Connectivity validation failed: %s'), $result->getError()));
}
return true;
// TODO: display diagnostics in $result->toArray() to the user
return ! $result->hasError();
}
}

View File

@ -154,20 +154,17 @@ class LdapResourceForm extends Form
*/
public static function isValidResource(Form $form)
{
try {
$resource = ResourceFactory::createResource(new ConfigObject($form->getValues()));
$resource->connect();
$resource->bind();
} catch (Exception $e) {
$msg = $form->translate('Connectivity validation failed, connection to the given resource not possible.');
if (($error = $e->getMessage())) {
$msg .= ' (' . $error . ')';
}
$form->addError($msg);
return false;
$result = ResourceFactory::createResource(new ConfigObject($form->getValues()))->inspect();
if ($result->hasError()) {
$form->addError(sprintf(
'%s (%s)',
$form->translate('Connectivity validation failed, connection to the given resource not possible.'),
$result->getError()
));
}
return true;
// TODO: display diagnostics in $result->toArray() to the user
return ! $result->hasError();
}
}

View File

@ -105,18 +105,15 @@ class DbBackendForm extends Form
*/
public static function isValidUserBackend(Form $form)
{
try {
$dbUserBackend = new DbUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
if ($dbUserBackend->select()->where('is_active', true)->count() < 1) {
$form->addError($form->translate('No active users found under the specified database backend'));
return false;
}
} catch (Exception $e) {
$form->addError(sprintf($form->translate('Using the specified backend failed: %s'), $e->getMessage()));
return false;
$backend = new DbUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
$result = $backend->inspect();
if ($result->hasError()) {
$form->addError(sprintf($form->translate('Using the specified backend failed: %s'), $result->getError()));
}
return true;
// TODO: display diagnostics in $result->toArray() to the user
return ! $result->hasError();
}
/**

View File

@ -4,6 +4,8 @@
namespace Icinga\Forms\Config\UserBackend;
use Exception;
use Icinga\Authentication\User\LdapUserBackend;
use Icinga\Data\Inspection;
use Icinga\Web\Form;
use Icinga\Data\ConfigObject;
use Icinga\Data\ResourceFactory;
@ -184,22 +186,16 @@ class LdapBackendForm extends Form
*/
public static function isValidUserBackend(Form $form)
{
try {
$ldapUserBackend = UserBackend::create(null, new ConfigObject($form->getValues()));
$ldapUserBackend->assertAuthenticationPossible();
} catch (AuthenticationException $e) {
if (($previous = $e->getPrevious()) !== null) {
$form->addError($previous->getMessage());
} else {
$form->addError($e->getMessage());
}
return false;
} catch (Exception $e) {
$form->addError(sprintf($form->translate('Unable to validate authentication: %s'), $e->getMessage()));
return false;
/**
* @var $result Inspection
*/
$result = UserBackend::create(null, new ConfigObject($form->getValues()))->inspect();
if ($result->hasError()) {
$form->addError($result->getError());
}
return true;
// TODO: display diagnostics in $result->toArray() to the user
return ! $result->hasError();
}
}

View File

@ -35,14 +35,14 @@ Please contact your distribution packagers.
You need to add the Icinga repository to your package management configuration for installing Icinga Web 2.
Below is a list with examples for various distributions.
Debian (debmon):
**Debian (debmon)**:
````
wget -O - http://debmon.org/debmon/repo.key 2>/dev/null | apt-key add -
echo 'deb http://debmon.org/debmon debmon-wheezy main' >/etc/apt/sources.list.d/debmon.list
apt-get update
````
Ubuntu Trusty:
**Ubuntu Trusty**:
````
wget -O - http://packages.icinga.org/icinga.key | apt-key add -
add-apt-repository 'deb http://packages.icinga.org/ubuntu icinga-trusty main'
@ -51,59 +51,72 @@ apt-get update
For other Ubuntu versions just replace trusty with your distribution's code name.
RHEL and CentOS:
**RHEL and CentOS**:
````
rpm --import http://packages.icinga.org/icinga.key
curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/epel/ICINGA-release.repo
yum makecache
````
Fedora:
**Fedora**:
````
rpm --import http://packages.icinga.org/icinga.key
curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/fedora/ICINGA-release.repo
yum makecache
````
SLES 11:
**SLES 11**:
````
zypper ar http://packages.icinga.org/SUSE/ICINGA-release-11.repo
zypper ref
````
SLES 12:
**SLES 12**:
````
zypper ar http://packages.icinga.org/SUSE/ICINGA-release.repo
zypper ref
````
openSUSE:
**openSUSE**:
````
zypper ar http://packages.icinga.org/openSUSE/ICINGA-release.repo
zypper ref
````
#### <a id="package-repositories-rhel-notes"></a> RHEL/CentOS Notes
The packages for RHEL/CentOS depend on other packages which are distributed as part of the
[EPEL repository](http://fedoraproject.org/wiki/EPEL). Please make sure to enable this repository by following
[these instructions](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F).
> Please note that installing Icinga Web 2 on **RHEL/CentOS 5** is not supported due to EOL versions of PHP and
> PostgreSQL.
#### <a id="package-repositories-wheezy-notes"></a> Debian wheezy Notes
The packages for Debian wheezy depend on other packages which are distributed as part of the
[wheezy-packports](http://backports.debian.org/) repository. Please make sure to enable this repository by following
[these instructions](http://backports.debian.org/Instructions/).
### <a id="installing-from-package-example"></a> Installing Icinga Web 2
You can install Icinga Web 2 by using your distribution's package manager to install the `icingaweb2` package.
Below is a list with examples for various distributions. The additional package `icingacli` is necessary
for being able to follow further steps in this guide.
Debian and Ubuntu:
**Debian and Ubuntu**:
````
apt-get install icingaweb2 icingacli
````
For Debian wheezy please read the [package repositories notes](#package-repositories-wheezy-notes).
RHEL, CentOS and Fedora:
**RHEL, CentOS and Fedora**:
````
yum install icingaweb2 icingacli
````
For RHEL/CentOS please read the [package repositories notes](#package-repositories-rhel-notes).
SLES and openSUSE:
**SLES and openSUSE**:
````
zypper install icingaweb2 icingacli
````
@ -158,12 +171,12 @@ mv icingaweb2 /usr/share/icingaweb2
Use `icingacli` to generate web server configuration for either Apache or nginx.
Apache:
**Apache**:
````
./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public
````
nginx:
**nginx**:
````
./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb2/public
````
@ -185,29 +198,29 @@ system group. The web server user and CLI user have to be added to this system g
Add the system group `icingaweb2` in the first place.
Fedora, RHEL, CentOS, SLES and OpenSUSE:
**Fedora, RHEL, CentOS, SLES and OpenSUSE**:
````
groupadd -r icingaweb2
````
Debian and Ubuntu:
**Debian and Ubuntu**:
````
addgroup --system icingaweb2
````
Add your web server's user to the system group `icingaweb2`:
Fedora, RHEL and CentOS:
**Fedora, RHEL and CentOS**:
````
usermod -a -G icingaweb2 apache
````
SLES and OpenSUSE:
**SLES and OpenSUSE**:
````
usermod -A icingaweb2 wwwrun
````
Debian and Ubuntu:
**Debian and Ubuntu**:
````
usermod -a -G icingaweb2 www-data
````
@ -271,3 +284,9 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
predefined subset of filter columns. Please see the module's security
related documentation for more details.
## <a id="upgrading-to-2.0.0"></a> Upgrading to Icinga Web 2 2.0.0
* Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through
the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library
through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package
`icingaweb2-vendor-zend`.

View File

@ -14,21 +14,12 @@ BuildArch: noarch
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
Packager: Icinga Team <info@icinga.org>
%if 0%{?fedora} || 0%{?rhel}
%if 0%{?fedora} || 0%{?rhel} || 0%{?amzn}
%define php php
%define php_cli php-cli
%define wwwconfigdir %{_sysconfdir}/httpd/conf.d
%define wwwuser apache
%if 0%{?rhel} == 5
%define php php53
%define php_cli php53-cli
%else
%define php php
%define php_cli php-cli
%endif
%if 0%{?rhel} == 6
%define zend php-ZendFramework
%else
%define zend %{name}-vendor-Zend
%endif
%define zend php-ZendFramework
%endif
%if 0%{?suse_version}
@ -44,6 +35,7 @@ Requires: apache2-mod_php5
%endif
%endif
%{?amzn:Requires(pre): shadow-utils}
%{?fedora:Requires(pre): shadow-utils}
%{?rhel:Requires(pre): shadow-utils}
%{?suse_version:Requires(pre): pwdutils}
@ -55,6 +47,7 @@ Requires: %{name}-vendor-JShrink
Requires: %{name}-vendor-lessphp
Requires: %{name}-vendor-Parsedown
Requires: %{zend}
Obsoletes: %{name}-vendor-zend
%description
@ -73,6 +66,7 @@ Icinga Web 2
%package common
Summary: Common files for Icinga Web 2 and the Icinga CLI
Group: Applications/System
%{?amzn:Requires(pre): shadow-utils}
%{?fedora:Requires(pre): shadow-utils}
%{?rhel:Requires(pre): shadow-utils}
%{?suse_version:Requires(pre): pwdutils}
@ -86,6 +80,7 @@ Summary: Icinga Web 2 PHP library
Group: Development/Libraries
Requires: %{php} >= 5.3.0
Requires: %{php}-gd %{php}-intl
%{?amzn:Requires: %{php}-pecl-imagick}
%{?fedora:Requires: php-pecl-imagick}
%{?rhel:Requires: php-pecl-imagick}
%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix}
@ -99,6 +94,7 @@ Summary: Icinga CLI
Group: Applications/System
Requires: %{name}-common = %{version}-%{release}
Requires: php-Icinga = %{version}-%{release}
%{?amzn:Requires: %{php_cli} >= 5.3.0 bash-completion}
%{?fedora:Requires: %{php_cli} >= 5.3.0 bash-completion}
%{?rhel:Requires: %{php_cli} >= 5.3.0 bash-completion}
%{?suse_version:Requires: %{php} >= 5.3.0}
@ -167,18 +163,6 @@ Requires: %{php} >= 5.3.0
Icinga Web 2 vendor library Parsedown
%package vendor-Zend
Version: 1.12.9
Release: 1%{?dist}
Summary: Icinga Web 2 vendor library Zend Framework
Group: Development/Libraries
License: BSD
Requires: %{php} >= 5.3.0
%description vendor-Zend
Icinga Web 2 vendor library Zend
%prep
%setup -q
@ -186,12 +170,12 @@ Icinga Web 2 vendor library Zend
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/{%{basedir}/{modules,library,public},%{bindir},%{configdir}/modules/setup,%{logdir},%{phpdir},%{wwwconfigdir},%{_sysconfdir}/bash_completion.d,%{docsdir}}
mkdir -p %{buildroot}/{%{basedir}/{modules,library/vendor,public},%{bindir},%{configdir}/modules/setup,%{logdir},%{phpdir},%{wwwconfigdir},%{_sysconfdir}/bash_completion.d,%{docsdir}}
cp -prv application doc %{buildroot}/%{basedir}
cp -pv etc/bash_completion.d/icingacli %{buildroot}/%{_sysconfdir}/bash_completion.d/icingacli
cp -prv modules/{monitoring,setup,doc,translation} %{buildroot}/%{basedir}/modules
cp -prv library/Icinga %{buildroot}/%{phpdir}
cp -prv library/vendor %{buildroot}/%{basedir}/library
cp -prv library/vendor/{dompdf,HTMLPurifier,JShrink,lessphp,Parsedown} %{buildroot}/%{basedir}/library/vendor
cp -prv public/{css,img,js,error_norewrite.html} %{buildroot}/%{basedir}/public
cp -pv packages/files/apache/icingaweb2.conf %{buildroot}/%{wwwconfigdir}/icingaweb2.conf
cp -pv packages/files/bin/icingacli %{buildroot}/%{bindir}
@ -222,12 +206,12 @@ rm -rf %{buildroot}
%{basedir}/doc
%{basedir}/modules
%{basedir}/public
%{wwwconfigdir}/icingaweb2.conf
%config(noreplace) %{wwwconfigdir}/icingaweb2.conf
%attr(2775,root,%{icingawebgroup}) %dir %{logdir}
%{docsdir}
%docdir %{docsdir}
%attr(2770,root,%{icingawebgroup}) %config(noreplace) %dir %{configdir}/modules/setup
%attr(0660,root,%{icingawebgroup}) %config(noreplace) %{configdir}/modules/setup/config.ini
%{docsdir}
%docdir %{docsdir}
%pre common
@ -277,8 +261,3 @@ exit 0
%files vendor-Parsedown
%defattr(-,root,root)
%{basedir}/library/vendor/Parsedown
%files vendor-Zend
%defattr(-,root,root)
%{basedir}/library/vendor/Zend

View File

@ -6,6 +6,7 @@ namespace Icinga\Authentication;
use Exception;
use Icinga\Authentication\UserGroup\UserGroupBackend;
use Icinga\Application\Config;
use Icinga\Data\ConfigObject;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError;
use Icinga\Application\Logger;
@ -63,8 +64,11 @@ class Manager
);
$config = new Config();
}
if ($config->get('preferences', 'store', 'ini') !== 'none') {
$preferencesConfig = $config->getSection('preferences');
if ($config->get('global', 'config_backend', 'ini') !== 'none') {
$preferencesConfig = new ConfigObject(array(
'store' => $config->get('global', 'config_backend', 'ini'),
'resource' => $config->get('global', 'config_resource')
));
try {
$preferencesStore = PreferencesStore::create(
$preferencesConfig,

View File

@ -4,13 +4,15 @@
namespace Icinga\Authentication\User;
use Exception;
use Icinga\Data\Inspectable;
use Icinga\Data\Inspection;
use PDO;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\AuthenticationException;
use Icinga\Repository\DbRepository;
use Icinga\User;
class DbUserBackend extends DbRepository implements UserBackendInterface
class DbUserBackend extends DbRepository implements UserBackendInterface, Inspectable
{
/**
* The algorithm to use when hashing passwords
@ -246,4 +248,26 @@ class DbUserBackend extends DbRepository implements UserBackendInterface
{
return crypt($password, self::HASH_ALGORITHM . ($salt !== null ? $salt : $this->generateSalt()));
}
/**
* Inspect this object to gain extended information about its health
*
* @return Inspection The inspection result
*/
public function inspect()
{
$insp = new Inspection('Db User Backend');
$insp->write($this->ds->inspect());
try {
$users = $this->select()->where('is_active', true)->count();
if ($users > 1) {
$insp->write(sprintf('%s active users', $users));
} else {
return $insp->error('0 active users', $users);
}
} catch (Exception $e) {
$insp->error(sprintf('Query failed: %s', $e->getMessage()));
}
return $insp;
}
}

View File

@ -5,6 +5,8 @@ namespace Icinga\Authentication\User;
use DateTime;
use Icinga\Data\ConfigObject;
use Icinga\Data\Inspectable;
use Icinga\Data\Inspection;
use Icinga\Exception\AuthenticationException;
use Icinga\Exception\ProgrammingError;
use Icinga\Repository\LdapRepository;
@ -13,7 +15,7 @@ use Icinga\Protocol\Ldap\LdapException;
use Icinga\Protocol\Ldap\Expression;
use Icinga\User;
class LdapUserBackend extends LdapRepository implements UserBackendInterface
class LdapUserBackend extends LdapRepository implements UserBackendInterface, Inspectable
{
/**
* The base DN to use for a query
@ -218,7 +220,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface
throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first');
}
if ($this->ds->getCapabilities()->hasAdOid()) {
if ($this->ds->getCapabilities()->isActiveDirectory()) {
$isActiveAttribute = 'userAccountControl';
$createdAtAttribute = 'whenCreated';
$lastModifiedAttribute = 'whenChanged';
@ -254,7 +256,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface
throw new ProgrammingError('It is required to set the objectClass where to look for users first');
}
if ($this->ds->getCapabilities()->hasAdOid()) {
if ($this->ds->getCapabilities()->isActiveDirectory()) {
$stateConverter = 'user_account_control';
} else {
$stateConverter = 'shadow_expire';
@ -306,41 +308,14 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface
}
/**
* Probe the backend to test if authentication is possible
*
* Try to bind to the backend and fetch a single user to check if:
* <ul>
* <li>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>
* @param Inspection $info Optional inspection to fill with diagnostic info
*
* @throws AuthenticationException When authentication is not possible
*/
public function assertAuthenticationPossible()
public function assertAuthenticationPossible(Inspection $insp = null)
{
try {
$result = $this->select()->fetchRow();
} catch (LdapException $e) {
throw new AuthenticationException('Connection not possible.', $e);
}
if ($result === false) {
throw new AuthenticationException(
'No objects with objectClass "%s" in DN "%s" found. (Filter: %s)',
$this->userClass,
$this->baseDn ?: $this->ds->getDn(),
$this->filter ?: 'None'
);
}
if (! isset($result->user_name)) {
throw new AuthenticationException(
'UserNameAttribute "%s" not existing in objectClass "%s"',
$this->userNameAttribute,
$this->userClass
);
}
}
/**
@ -377,4 +352,58 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface
);
}
}
/**
* Inspect if this LDAP User Backend is working as expected by probing the backend
* and testing if thea uthentication is possible
*
* Try to bind to the backend and fetch a single user to check if:
* <ul>
* <li>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>
*
* @return Inspection Inspection result
*/
public function inspect()
{
$result = new Inspection('Ldap User Backend');
// inspect the used connection to get more diagnostic info in case the connection is not working
$result->write($this->ds->inspect());
try {
try {
$res = $this->select()->fetchRow();
} catch (LdapException $e) {
throw new AuthenticationException('Connection not possible', $e);
}
$result->write('Searching for: ' . sprintf(
'objectClass "%s" in DN "%s" (Filter: %s)',
$this->userClass,
$this->baseDn ?: $this->ds->getDn(),
$this->filter ?: 'None'
));
if ($res === false) {
throw new AuthenticationException('Error, no users found in backend');
}
$result->write(sprintf('%d users found in backend', $this->select()->count()));
if (! isset($res->user_name)) {
throw new AuthenticationException(
'UserNameAttribute "%s" not existing in objectClass "%s"',
$this->userNameAttribute,
$this->userClass
);
}
} catch (AuthenticationException $e) {
if (($previous = $e->getPrevious()) !== null) {
$result->error($previous->getMessage());
} else {
$result->error($e->getMessage());
}
} catch (Exception $e) {
$result->error(sprintf('Unable to validate authentication: %s', $e->getMessage()));
}
return $result;
}
}

View File

@ -229,12 +229,12 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa
*/
protected function persistGroupId($groupName)
{
if (! $groupName || empty($groupName) || is_int($groupName)) {
if (! $groupName || empty($groupName) || is_numeric($groupName)) {
return $groupName;
}
if (is_array($groupName)) {
if (is_int($groupName[0])) {
if (is_numeric($groupName[0])) {
return $groupName; // In case the array contains mixed types...
}

View File

@ -3,6 +3,9 @@
namespace Icinga\Data\Db;
use Exception;
use Icinga\Data\Inspectable;
use Icinga\Data\Inspection;
use PDO;
use Iterator;
use Zend_Db;
@ -23,7 +26,7 @@ use Icinga\Exception\ProgrammingError;
/**
* Encapsulate database connections and query creation
*/
class DbConnection implements Selectable, Extensible, Updatable, Reducible
class DbConnection implements Selectable, Extensible, Updatable, Reducible, Inspectable
{
/**
* Connection config
@ -435,4 +438,42 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible
return $column . ' ' . $sign . ' ' . $this->dbAdapter->quote($value);
}
}
public function inspect()
{
$insp = new Inspection('Db Connection');
try {
$this->getDbAdapter()->getConnection();
$config = $this->dbAdapter->getConfig();
$insp->write(sprintf(
'Connection to %s as %s on %s:%s successful',
$config['dbname'],
$config['username'],
$config['host'],
$config['port']
));
switch ($this->dbType) {
case 'mysql':
$rows = $this->dbAdapter->query(
'SHOW VARIABLES WHERE variable_name ' .
'IN (\'version\', \'protocol_version\', \'version_compile_os\');'
)->fetchAll();
$sqlinsp = new Inspection('MySQL');
foreach ($rows as $row) {
$sqlinsp->write($row->variable_name . ': ' . $row->value);
}
$insp->write($sqlinsp);
break;
case 'pgsql':
$row = $this->dbAdapter->query('SELECT version();')->fetchAll();
$sqlinsp = new Inspection('PostgreSQL');
$sqlinsp->write($row[0]->version);
$insp->write($sqlinsp);
break;
}
} catch (Exception $e) {
return $insp->error(sprintf('Connection failed %s', $e->getMessage()));
}
return $insp;
}
}

View File

@ -0,0 +1,20 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Data;
/**
* An object for which the user can retrieve status information
*
* This interface is useful for providing summaries or diagnostic information about objects
* to users.
*/
interface Inspectable
{
/**
* Inspect this object to gain extended information about its health
*
* @return Inspection The inspection result
*/
public function inspect();
}

View File

@ -0,0 +1,127 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Data;
use Icinga\Application\Logger;
use Icinga\Exception\ProgrammingError;
/**
* Contains information about an object in the form of human-readable log entries and indicates if the object has errors
*/
class Inspection
{
/**
* @var array
*/
protected $log = array();
/**
* @var string
*/
protected $description;
/**
* @var string|Inspection
*/
protected $error;
/**
* @param $description Describes the object that is being inspected
*/
public function __construct($description)
{
$this->description = $description;
}
/**
* Get the name of this Inspection
*
* @return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* Append the given log entry or nested inspection
*
* @throws ProgrammingError When called after erroring
*
* @param $entry string|Inspection A log entry or nested inspection
*/
public function write($entry)
{
if (isset($this->error)) {
throw new ProgrammingError('Inspection object used after error');
}
if ($entry instanceof Inspection) {
$this->log[$entry->description] = $entry->toArray();
} else {
$this->log[] = $entry;
}
}
/**
* Append the given log entry and fail this inspection with the given error
*
* @param $entry string|Inspection A log entry or nested inspection
*
* @throws ProgrammingError When called multiple times
*
* @return this fluent interface
*/
public function error($entry)
{
if (isset($this->error)) {
throw new ProgrammingError('Inspection object used after error');
}
$this->write($entry);
$this->error = $entry;
return $this;
}
/**
* If the inspection resulted in an error
*
* @return bool
*/
public function hasError()
{
return isset($this->error);
}
/**
* The error that caused the inspection to fail
*
* @return Inspection|string
*/
public function getError()
{
return $this->error;
}
/**
* Convert the inspection to an array
*
* @return array An array of strings that describe the state in a human-readable form, each array element
* represents one log entry about this object.
*/
public function toArray()
{
return $this->log;
}
/**
* Return a text representation of the inspection log entries
*/
public function __toString()
{
return sprintf(
'Inspection: description: "%s" error: "%s"',
$this->description,
$this->error
);
}
}

View File

@ -13,13 +13,6 @@ class Discovery {
*/
private $connection;
/**
* If discovery was already performed
*
* @var bool
*/
private $discovered = false;
/**
* @param LdapConnection $conn The ldap connection to use for the discovery
*/
@ -28,17 +21,6 @@ class Discovery {
$this->connection = $conn;
}
/**
* Execute the discovery on the underlying connection
*/
private function execDiscovery()
{
if (! $this->discovered) {
$this->connection->connect();
$this->discovered = true;
}
}
/**
* Suggests a resource configuration of hostname, port and root_dn
* based on the discovery
@ -47,10 +29,6 @@ class Discovery {
*/
public function suggestResourceSettings()
{
if (! $this->discovered) {
$this->execDiscovery();
}
return array(
'hostname' => $this->connection->getHostname(),
'port' => $this->connection->getPort(),
@ -66,7 +44,6 @@ class Discovery {
*/
public function suggestBackendSettings()
{
$this->execDiscovery();
if ($this->isAd()) {
return array(
'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
@ -89,8 +66,7 @@ class Discovery {
*/
public function isAd()
{
$this->execDiscovery();
return $this->connection->getCapabilities()->hasAdOid();
return $this->connection->getCapabilities()->isActiveDirectory();
}
/**
@ -100,7 +76,6 @@ class Discovery {
*/
public function isSuccess()
{
$this->execDiscovery();
return $this->connection->discoverySuccessful();
}

View File

@ -9,9 +9,8 @@ namespace Icinga\Protocol\Ldap;
* Provides information about the available encryption mechanisms (StartTLS), the supported
* LDAP protocol (v2/v3), vendor-specific extensions or protocols controls and extensions.
*/
class Capability
class LdapCapabilities
{
const LDAP_SERVER_START_TLS_OID = '1.3.6.1.4.1.1466.20037';
const LDAP_PAGED_RESULT_OID_STRING = '1.2.840.113556.1.4.319';
@ -127,7 +126,7 @@ class Capability
}
/**
* Return if the capability object contains support for StartTLS
* Return if the capability object contains support for paged results
*
* @return bool Whether StartTLS is supported
*/
@ -141,11 +140,22 @@ class Capability
*
* @return boolean
*/
public function hasAdOid()
public function isActiveDirectory()
{
return isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_OID]);
}
/**
* Whether the ldap server is an OpenLDAP server
*
* @return bool
*/
public function isOpenLdap()
{
return isset($this->attributes->structuralObjectClass) &&
$this->attributes->structuralObjectClass === 'OpenLDAProotDSE';
}
/**
* Return if the capability objects contains support for LdapV3, defaults to true if discovery failed
*
@ -208,4 +218,121 @@ class Capability
}
return$this->attributes->namingContexts;
}
public function getVendor()
{
/*
rfc #3045 specifies that the name of the server MAY be included in the attribute 'verndorName',
AD and OpenLDAP don't do this, but for all all other vendors we follow the standard and
just hope for the best.
*/
if ($this->isActiveDirectory()) {
return 'Microsoft Active Directory';
}
if ($this->isOpenLdap()) {
return 'OpenLDAP';
}
if (! isset($this->attributes->vendorName)) {
return null;
}
return $this->attributes->vendorName;
}
public function getVersion()
{
/*
rfc #3045 specifies that the version of the server MAY be included in the attribute 'vendorVersion',
but AD and OpenLDAP don't do this. For OpenLDAP there is no way to query the server versions, but for all
all other vendors we follow the standard and just hope for the best.
*/
if ($this->isActiveDirectory()) {
return $this->getAdObjectVersionName();
}
if (! isset($this->attributes->vendorVersion)) {
return null;
}
return $this->attributes->vendorVersion;
}
/**
* Discover the capabilities of the given LDAP server
*
* @param LdapConnection $connection The ldap connection to use
*
* @return LdapCapabilities
*
* @throws LdapException In case the capability query has failed
*/
public static function discoverCapabilities(LdapConnection $connection)
{
$ds = $connection->getConnection();
$fields = array(
'defaultNamingContext',
'namingContexts',
'vendorName',
'vendorVersion',
'supportedSaslMechanisms',
'dnsHostName',
'schemaNamingContext',
'supportedLDAPVersion', // => array(3, 2)
'supportedCapabilities',
'supportedControl',
'supportedExtension',
'objectVersion',
'+'
);
$result = @ldap_read($ds, '', (string) $connection->select()->from('*', $fields), $fields);
if (! $result) {
throw new LdapException(
'Capability query failed (%s:%d): %s. Check if hostname and port of the'
. ' ldap resource are correct and if anonymous access is permitted.',
$connection->getHostname(),
$connection->getPort(),
ldap_error($ds)
);
}
$entry = ldap_first_entry($ds, $result);
if ($entry === false) {
throw new LdapException(
'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.',
$connection->getHostname(),
$connection->getPort(),
ldap_error($ds)
);
}
$cap = new LdapCapabilities(
$connection->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
)
);
return $cap;
}
/**
* Determine the active directory version using the available capabillities
*
* @return null|string The server version description or null when unknown
*/
protected function getAdObjectVersionName()
{
if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_W8_OID])) {
return 'Windows Server 2012 (or newer)';
}
if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID])) {
return 'Windows Server 2008 R2 (or newer)';
}
if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V60_OID])) {
return 'Windows Server 2008 (or newer)';
}
return null;
}
}

View File

@ -3,20 +3,24 @@
namespace Icinga\Protocol\Ldap;
use Exception;
use ArrayIterator;
use Icinga\Application\Config;
use Icinga\Application\Logger;
use Icinga\Application\Platform;
use Icinga\Data\ConfigObject;
use Icinga\Data\Inspectable;
use Icinga\Data\Inspection;
use Icinga\Data\Selectable;
use Icinga\Data\Sortable;
use Icinga\Exception\InspectionException;
use Icinga\Exception\ProgrammingError;
use Icinga\Protocol\Ldap\LdapException;
/**
* Encapsulate LDAP connections and query creation
*/
class LdapConnection implements Selectable
class LdapConnection implements Selectable, Inspectable
{
/**
* Indicates that the target object cannot be found
@ -142,17 +146,24 @@ class LdapConnection implements Selectable
/**
* The properties and capabilities of the LDAP server
*
* @var Capability
* @var LdapCapabilities
*/
protected $capabilities;
/**
* Whether discovery was successful or not
* Whether discovery was successful
*
* @var bool
*/
protected $discoverySuccess;
/**
* Whether the current connection is encrypted
*
* @var bool
*/
protected $encrypted = null;
/**
* Create a new connection object
*
@ -217,46 +228,88 @@ class LdapConnection implements Selectable
return $this->root;
}
/**
* Return the LDAP link identifier being used
*
* Establishes a connection if necessary.
*
* @return resource
*/
public function getConnection()
{
if ($this->ds === null) {
$this->ds = $this->prepareNewConnection();
}
return $this->ds;
}
/**
* Return the capabilities of the current connection
*
* @return Capability
* @return LdapCapabilities
*/
public function getCapabilities()
{
if ($this->capabilities === null) {
$this->connect(); // Populates $this->capabilities
try {
$this->capabilities = LdapCapabilities::discoverCapabilities($this);
$this->discoverySuccess = true;
} catch (LdapException $e) {
Logger::debug($e);
Logger::warning('LADP discovery failed, assuming default LDAP capabilities.');
$this->capabilities = new LdapCapabilities(); // create empty default capabilities
$this->discoverySuccess = false;
}
}
return $this->capabilities;
}
/**
* Return whether discovery was successful or not
* Return whether discovery was successful
*
* @return bool true if the capabilities were successfully determined, false if the capabilities were guessed
*/
public function discoverySuccessful()
{
if ($this->discoverySuccess === null) {
$this->getCapabilities(); // Initializes self::$discoverySuccess
}
return $this->discoverySuccess;
}
/**
* Return whether the current connection is encrypted
*
* @return bool
*/
public function isEncrypted()
{
if ($this->encrypted === null) {
return false;
}
return $this->encrypted;
}
/**
* Establish a connection
*
* @throws LdapException In case the connection could not be established
*
* @deprecated The connection is established lazily now
*/
public function connect()
{
if ($this->ds === null) {
$this->ds = $this->prepareNewConnection();
}
$this->getConnection();
}
/**
* Perform a LDAP bind on the current connection
*
* @throws LdapException In case the LDAP bind was unsuccessful
* @throws LdapException In case the LDAP bind was unsuccessful or insecure
*/
public function bind()
{
@ -264,7 +317,9 @@ class LdapConnection implements Selectable
return;
}
$success = @ldap_bind($this->ds, $this->bindDn, $this->bindPw);
$ds = $this->getConnection();
$success = @ldap_bind($ds, $this->bindDn, $this->bindPw);
if (! $success) {
throw new LdapException(
'LDAP connection to %s:%s (%s / %s) failed: %s',
@ -272,7 +327,7 @@ class LdapConnection implements Selectable
$this->port,
$this->bindDn,
'***' /* $this->bindPw */,
ldap_error($this->ds)
ldap_error($ds)
);
}
@ -310,7 +365,6 @@ class LdapConnection implements Selectable
*/
public function count(LdapQuery $query)
{
$this->connect();
$this->bind();
$res = $this->runQuery($query, array());
@ -327,11 +381,9 @@ class LdapConnection implements Selectable
*/
public function fetchAll(LdapQuery $query, array $fields = null)
{
$this->connect();
$this->bind();
if (
$query->getUsePagedResults()
if ($query->getUsePagedResults()
&& version_compare(PHP_VERSION, '5.4.0') >= 0
&& $this->getCapabilities()->hasPagedResult()
) {
@ -465,21 +517,20 @@ class LdapConnection implements Selectable
*/
public function testCredentials($bindDn, $bindPw)
{
$this->connect();
$success = @ldap_bind($this->ds, $bindDn, $bindPw);
$ds = $this->getConnection();
$success = @ldap_bind($ds, $bindDn, $bindPw);
if (! $success) {
if (ldap_errno($this->ds) === self::LDAP_INVALID_CREDENTIALS) {
if (ldap_errno($ds) === self::LDAP_INVALID_CREDENTIALS) {
Logger::debug(
'Testing LDAP credentials (%s / %s) failed: %s',
$bindDn,
'***',
ldap_error($this->ds)
ldap_error($ds)
);
return false;
}
throw new LdapException(ldap_error($this->ds));
throw new LdapException(ldap_error($ds));
}
return true;
@ -494,11 +545,11 @@ class LdapConnection implements Selectable
*/
public function hasDn($dn)
{
$this->connect();
$this->bind();
$result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass'));
return ldap_count_entries($this->ds, $result) > 0;
$ds = $this->getConnection();
$result = ldap_read($ds, $dn, '(objectClass=*)', array('objectClass'));
return ldap_count_entries($ds, $result) > 0;
}
/**
@ -512,19 +563,19 @@ class LdapConnection implements Selectable
*/
public function deleteRecursively($dn)
{
$this->connect();
$this->bind();
$result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass'));
$ds = $this->getConnection();
$result = @ldap_list($ds, $dn, '(objectClass=*)', array('objectClass'));
if ($result === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return false;
}
throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($this->ds));
throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($ds));
}
$children = ldap_get_entries($this->ds, $result);
$children = ldap_get_entries($ds, $result);
for ($i = 0; $i < $children['count']; $i++) {
$result = $this->deleteRecursively($children[$i]['dn']);
if (! $result) {
@ -547,16 +598,16 @@ class LdapConnection implements Selectable
*/
public function deleteDn($dn)
{
$this->connect();
$this->bind();
$result = @ldap_delete($this->ds, $dn);
$ds = $this->getConnection();
$result = @ldap_delete($ds, $dn);
if ($result === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return false; // TODO: Isn't it a success if something i'd like to remove is not existing at all???
}
throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($this->ds));
throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($ds));
}
return true;
@ -600,11 +651,13 @@ class LdapConnection implements Selectable
$fields = $query->getColumns();
}
$ds = $this->getConnection();
$serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID);
if ($serverSorting && $query->hasOrder()) {
ldap_set_option($this->ds, LDAP_OPT_SERVER_CONTROLS, array(
ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(
array(
'oid' => Capability::LDAP_SERVER_SORT_OID,
'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID,
'value' => $this->encodeSortRules($query->getOrder())
)
));
@ -617,7 +670,7 @@ class LdapConnection implements Selectable
}
$results = @ldap_search(
$this->ds,
$ds,
$query->getBase() ?: $this->rootDn,
(string) $query,
array_values($fields),
@ -625,7 +678,7 @@ class LdapConnection implements Selectable
$serverSorting && $limit ? $offset + $limit : 0
);
if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return array();
}
@ -633,25 +686,25 @@ class LdapConnection implements Selectable
'LDAP query "%s" (base %s) failed. Error: %s',
$query,
$query->getBase() ?: $this->rootDn,
ldap_error($this->ds)
ldap_error($ds)
);
} elseif (ldap_count_entries($this->ds, $results) === 0) {
} elseif (ldap_count_entries($ds, $results) === 0) {
return array();
}
$count = 0;
$entries = array();
$entry = ldap_first_entry($this->ds, $results);
$entry = ldap_first_entry($ds, $results);
do {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry), array_flip($fields)
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
);
}
} while (
(! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($this->ds, $entry))
} while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($ds, $entry))
);
if (! $serverSorting && $query->hasOrder()) {
@ -693,11 +746,13 @@ class LdapConnection implements Selectable
$fields = $query->getColumns();
}
$ds = $this->getConnection();
$serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID);
if ($serverSorting && $query->hasOrder()) {
ldap_set_option($this->ds, LDAP_OPT_SERVER_CONTROLS, array(
ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(
array(
'oid' => Capability::LDAP_SERVER_SORT_OID,
'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID,
'value' => $this->encodeSortRules($query->getOrder())
)
));
@ -715,10 +770,10 @@ class LdapConnection implements Selectable
do {
// Do not request the pagination control as a critical extension, as we want the
// server to return results even if the paged search request cannot be satisfied
ldap_control_paged_result($this->ds, $pageSize, false, $cookie);
ldap_control_paged_result($ds, $pageSize, false, $cookie);
$results = @ldap_search(
$this->ds,
$ds,
$base,
$queryString,
array_values($fields),
@ -726,7 +781,7 @@ class LdapConnection implements Selectable
$serverSorting && $limit ? $offset + $limit : 0
);
if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
break;
}
@ -734,37 +789,38 @@ class LdapConnection implements Selectable
'LDAP query "%s" (base %s) failed. Error: %s',
$queryString,
$base,
ldap_error($this->ds)
ldap_error($ds)
);
} elseif (ldap_count_entries($this->ds, $results) === 0) {
} elseif (ldap_count_entries($ds, $results) === 0) {
if (in_array(
ldap_errno($this->ds),
ldap_errno($ds),
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
)) {
Logger::warning(
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
$count,
ldap_error($this->ds)
ldap_error($ds)
);
}
break;
}
$entry = ldap_first_entry($this->ds, $results);
$entry = ldap_first_entry($ds, $results);
do {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry), array_flip($fields)
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
);
}
} while (
(! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($this->ds, $entry))
&& ($entry = ldap_next_entry($ds, $entry))
);
if (false === @ldap_control_paged_result_response($this->ds, $results, $cookie)) {
if (false === @ldap_control_paged_result_response($ds, $results, $cookie)) {
// If the page size is greater than or equal to the sizeLimit value, the server should ignore the
// control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt
// This applies no matter whether paged search requests are permitted or not. You're done once you
@ -785,11 +841,11 @@ class LdapConnection implements Selectable
// A sequence of paged search requests is abandoned by the client sending a search request containing a
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
// the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($this->ds, 0, false, $cookie);
ldap_search($this->ds, $base, $queryString); // Returns no entries, due to the page size
ldap_control_paged_result($ds, 0, false, $cookie);
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
} else {
// Reset the paged search request so that subsequent requests succeed
ldap_control_paged_result($this->ds, 0);
ldap_control_paged_result($ds, 0);
}
if (! $serverSorting && $query->hasOrder()) {
@ -812,7 +868,7 @@ class LdapConnection implements Selectable
*
* @return object
*/
protected function cleanupAttributes($attributes, array $requestedFields)
public function cleanupAttributes($attributes, array $requestedFields)
{
// In case the result contains attributes with a differing case than the requested fields, it is
// necessary to create another array to map attributes case insensitively to their requested counterparts.
@ -862,6 +918,7 @@ class LdapConnection implements Selectable
* @param array $sortRules
*
* @return string
* @throws ProgrammingError
*
* @todo Produces an invalid stream, obviously
*/
@ -887,63 +944,56 @@ class LdapConnection implements Selectable
/**
* Prepare and establish a connection with the LDAP server
*
* @return resource A positive LDAP link identifier
* @param Inspection $info Optional inspection to fill with diagnostic info
*
* @throws LdapException In case the connection is not possible
* @return resource A LDAP link identifier
*
* @throws LdapException In case the connection is not possible
*/
protected function prepareNewConnection()
protected function prepareNewConnection(Inspection $info = null)
{
if (! isset($info)) {
$info = new Inspection('');
}
if ($this->encryption === static::STARTTLS || $this->encryption === static::LDAPS) {
$this->prepareTlsEnvironment();
}
$hostname = $this->hostname;
if ($this->encryption === static::LDAPS) {
$info->write('Connect using LDAPS');
if (! $this->validateCertificate) {
$info->write('Skip certificate validation');
}
$hostname = 'ldaps://' . $hostname;
}
$ds = ldap_connect($hostname, $this->port);
try {
$this->capabilities = $this->discoverCapabilities($ds);
$this->discoverySuccess = true;
} catch (LdapException $e) {
Logger::debug($e);
Logger::warning('LADP discovery failed, assuming default LDAP capabilities.');
$this->capabilities = new Capability(); // create empty default capabilities
$this->discoverySuccess = false;
}
if ($this->encryption === static::STARTTLS) {
$force_tls = false;
if ($this->capabilities->hasStartTls()) {
if (@ldap_start_tls($ds)) {
Logger::debug('LDAP STARTTLS succeeded');
} else {
Logger::error('LDAP STARTTLS failed: %s', ldap_error($ds));
throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds));
}
} elseif ($force_tls) {
throw new LdapException('STARTTLS is required but not announced by %s', $this->hostname);
} else {
Logger::warning('LDAP STARTTLS enabled but not announced');
}
}
// ldap_rename requires LDAPv3:
if ($this->capabilities->hasLdapV3()) {
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
throw new LdapException('LDAPv3 is required');
}
} else {
// TODO: remove this -> FORCING v3 for now
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
Logger::warning('No LDAPv3 support detected');
}
// Usage of ldap_rename, setting LDAP_OPT_REFERRALS to 0 or using STARTTLS requires LDAPv3.
// If this does not work we're probably not in a PHP 5.3+ environment as it is VERY
// unlikely that the server complains about it by itself prior to a bind request
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
// Not setting this results in "Operations error" on AD when using the whole domain as search base
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
// ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER);
if ($this->encryption === static::STARTTLS) {
$this->encrypted = true;
$info->write('Connect using STARTTLS');
if (! $this->validateCertificate) {
$info->write('Skip certificate validation');
}
if (! ldap_start_tls($ds)) {
throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds));
}
} elseif ($this->encryption !== static::LDAPS) {
$this->encrypted = false;
$info->write('Connect without encryption');
}
return $ds;
}
@ -971,56 +1021,6 @@ class LdapConnection implements Selectable
}
}
/**
* Discover the capabilities of the given LDAP server
*
* @param resource $ds The link identifier of the current LDAP connection
*
* @return Capability
*
* @throws LdapException In case the capability query has failed
*/
protected function discoverCapabilities($ds)
{
$fields = array(
'defaultNamingContext',
'namingContexts',
'vendorName',
'vendorVersion',
'supportedSaslMechanisms',
'dnsHostName',
'schemaNamingContext',
'supportedLDAPVersion', // => array(3, 2)
'supportedCapabilities',
'supportedControl',
'supportedExtension',
'+'
);
$result = @ldap_read($ds, '', (string) $this->select()->from('*', $fields), $fields);
if (! $result) {
throw new LdapException(
'Capability query failed (%s:%d): %s. Check if hostname and port of the'
. ' ldap resource are correct and if anonymous access is permitted.',
$this->hostname,
$this->port,
ldap_error($ds)
);
}
$entry = ldap_first_entry($ds, $result);
if ($entry === false) {
throw new LdapException(
'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.',
$this->hostname,
$this->port,
ldap_error($ds)
);
}
return new Capability($this->cleanupAttributes(ldap_get_attributes($ds, $entry), array_flip($fields)));
}
/**
* Create an LDAP entry
*
@ -1031,7 +1031,7 @@ class LdapConnection implements Selectable
*/
public function addEntry($dn, array $attributes)
{
return ldap_add($this->ds, $dn, $attributes);
return ldap_add($this->getConnection(), $dn, $attributes);
}
/**
@ -1044,7 +1044,7 @@ class LdapConnection implements Selectable
*/
public function modifyEntry($dn, array $attributes)
{
return ldap_modify($this->ds, $dn, $attributes);
return ldap_modify($this->getConnection(), $dn, $attributes);
}
/**
@ -1060,9 +1060,10 @@ class LdapConnection implements Selectable
*/
public function moveEntry($dn, $newRdn, $newParentDn)
{
$result = ldap_rename($this->ds, $dn, $newRdn, $newParentDn, false);
$ds = $this->getConnection();
$result = ldap_rename($ds, $dn, $newRdn, $newParentDn, false);
if ($result === false) {
throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($this->ds));
throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($ds));
}
return $result;
@ -1085,6 +1086,57 @@ class LdapConnection implements Selectable
return $dir;
}
/**
* Inspect if this LDAP Connection is working as expected
*
* Check if connection, bind and encryption is working as expected and get additional
* information about the used
*
* @return Inspection Inspection result
*/
public function inspect()
{
$insp = new Inspection('Ldap Connection');
// Try to connect to the server with the given connection parameters
try {
$ds = $this->prepareNewConnection($insp);
} catch (Exception $e) {
return $insp->error($e->getMessage());
}
// Try a bind-command with the given user credentials, this must not fail
$success = @ldap_bind($ds, $this->bindDn, $this->bindPw);
$msg = sprintf(
'LDAP bind to %s:%s (%s / %s)',
$this->hostname,
$this->port,
$this->bindDn,
'***' /* $this->bindPw */
);
if (! $success) {
return $insp->error(sprintf('%s failed: %s', $msg, ldap_error($ds)));
}
$insp->write(sprintf($msg . ' successful'));
// Try to execute a schema discovery this may fail if schema discovery is not supported
try {
$cap = LdapCapabilities::discoverCapabilities($this);
$discovery = new Inspection('Discovery Results');
$discovery->write($cap->getVendor());
$version = $cap->getVersion();
if (isset($version)) {
$discovery->write($version);
}
$discovery->write('Supports STARTTLS: ' . ($cap->hasStartTls() ? 'True' : 'False'));
$discovery->write('Default naming context: ' . $cap->getDefaultNamingContext());
$insp->write($discovery);
} catch (Exception $e) {
$insp->write('Schema discovery not possible: ' . $e->getMessage());
}
return $insp;
}
/**
* Reset the environment variables set by self::prepareTlsEnvironment()
*/

View File

@ -497,19 +497,24 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Return the name of the conversion method for the given alias or column name and context
*
* @param array|string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve
* If a query column or a filter column, which is part of a query filter, needs to be converted,
* you'll need to pass $query, otherwise the column is considered a statement column.
*
* @param string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve
* @param RepositoryQuery $query If given the column is considered a query column,
* statement column otherwise
*
* @return string
*
* @throws ProgrammingError In case a conversion rule is found but not any conversion method
*/
protected function getConverter($table, $name, $context)
protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
{
if (
$this->validateQueryColumnAssociation($table, $name)
|| $this->validateStatementColumnAssociation($table, $name)
($query !== null && $this->validateQueryColumnAssociation($table, $name))
|| ($query === null && $this->validateStatementColumnAssociation($table, $name))
) {
$table = $this->removeTablePrefix($this->clearTableAlias($table));
} else {
@ -519,7 +524,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
}
}
return parent::getConverter($table, $name, $context);
return parent::getConverter($table, $name, $context, $query);
}
/**
@ -650,7 +655,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
* Attempts to join the given column from a different table if its association to the given table cannot be
* verified.
*
* @param string $table The table where to look for the column or alias
* @param array|string $table The table where to look for the column or alias
* @param string $name The name or alias of the column to validate
* @param RepositoryQuery $query An optional query to pass as context,
* if not given no join will be attempted
@ -662,7 +667,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
{
if ($query === null || $this->validateQueryColumnAssociation($table, $name)) {
return parent::requireQueryColumn($table, $name, $query);
return parent::requireQueryColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
}
return $this->joinColumn($name, $table, $query);
@ -674,7 +679,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
* Attempts to join the given column from a different table if its association to the given table cannot be
* verified.
*
* @param string $table The table where to look for the column or alias
* @param array|string $table The table where to look for the column or alias
* @param string $name The name or alias of the column to validate
* @param RepositoryQuery $query An optional query to pass as context,
* if not given the column is considered being used for a statement filter
@ -690,7 +695,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
}
if ($this->validateQueryColumnAssociation($table, $name)) {
return parent::requireFilterColumn($table, $name, $query);
return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
}
return $this->joinColumn($name, $table, $query);
@ -699,8 +704,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Return the statement column name for the given alias or null in case the alias does not exist
*
* @param string $table
* @param string $alias
* @param array|string $table
* @param string $alias
*
* @return string|null
*/
@ -711,7 +716,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
return $statementAliasColumnMap[$alias];
}
$prefixedAlias = $this->removeTablePrefix($table) . '.' . $alias;
$prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias;
if (isset($statementAliasColumnMap[$prefixedAlias])) {
return $statementAliasColumnMap[$prefixedAlias];
}
@ -720,8 +725,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Return the alias for the given statement column name or null in case the statement column does not exist
*
* @param string $table
* @param string $column
* @param array|string $table
* @param string $column
*
* @return string|null
*/
@ -732,7 +737,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
return $statementColumnAliasMap[$column];
}
$prefixedColumn = $this->removeTablePrefix($table) . '.' . $column;
$prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column;
if (isset($statementColumnAliasMap[$prefixedColumn])) {
return $statementColumnAliasMap[$prefixedColumn];
}
@ -741,14 +746,14 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Return whether the given alias or statement column name is available in the given table
*
* @param string $table
* @param string $alias
* @param array|string $table
* @param string $alias
*
* @return bool
*/
public function validateStatementColumnAssociation($table, $alias)
{
$tableName = $this->removeTablePrefix($table);
$tableName = $this->removeTablePrefix($this->clearTableAlias($table));
$statementAliasTableMap = $this->getStatementAliasTableMap();
if (isset($statementAliasTableMap[$alias])) {
@ -767,8 +772,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Return whether the given column name or alias of the given table is a valid statement column
*
* @param string $table The table where to look for the column or alias
* @param string $name The column name or alias to check
* @param array|string $table The table where to look for the column or alias
* @param string $name The column name or alias to check
*
* @return bool
*/
@ -779,7 +784,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
&& $this->reassembleStatementColumnAlias($table, $name) === null)
|| !$this->validateStatementColumnAssociation($table, $name)
) {
return parent::hasStatementColumn($table, $name);
return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
}
return true;
@ -788,12 +793,12 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/**
* Validate that the given column is a valid statement column and return it or the actual name if it's an alias
*
* @param string $table The table for which to require the column
* @param string $name The name or alias of the column to validate
* @param array|string $table The table for which to require the column
* @param string $name The name or alias of the column to validate
*
* @return string The given column's name
* @return string The given column's name
*
* @throws StatementException In case the given column is not a statement column
* @throws StatementException In case the given column is not a statement column
*/
public function requireStatementColumn($table, $name)
{
@ -802,11 +807,15 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
} elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) {
$column = $name;
} else {
return parent::requireStatementColumn($table, $name);
return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
}
if (! $this->validateStatementColumnAssociation($table, $alias)) {
throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
throw new StatementException(
'Statement column "%s" not found in table "%s"',
$name,
$this->removeTablePrefix($this->clearTableAlias($table))
);
}
return $column;

View File

@ -491,15 +491,18 @@ abstract class Repository implements Selectable
/**
* Convert a value supposed to be transmitted to the data source
*
* @param string $table The table where to persist the value
* @param string $name The alias or column name
* @param mixed $value The value to convert
* @param string $table The table where to persist the value
* @param string $name The alias or column name
* @param mixed $value The value to convert
* @param RepositoryQuery $query An optional query to pass as context
* (Directly passed through to $this->getConverter)
*
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value
* @return mixed If conversion was possible, the converted value,
* otherwise the unchanged value
*/
public function persistColumn($table, $name, $value)
public function persistColumn($table, $name, $value, RepositoryQuery $query = null)
{
$converter = $this->getConverter($table, $name, 'persist');
$converter = $this->getConverter($table, $name, 'persist', $query);
if ($converter !== null) {
$value = $this->$converter($value);
}
@ -510,15 +513,18 @@ abstract class Repository implements Selectable
/**
* Convert a value which was fetched from the data source
*
* @param string $table The table the value has been fetched from
* @param string $name The alias or column name
* @param mixed $value The value to convert
* @param string $table The table the value has been fetched from
* @param string $name The alias or column name
* @param mixed $value The value to convert
* @param RepositoryQuery $query An optional query to pass as context
* (Directly passed through to $this->getConverter)
*
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value
* @return mixed If conversion was possible, the converted value,
* otherwise the unchanged value
*/
public function retrieveColumn($table, $name, $value)
public function retrieveColumn($table, $name, $value, RepositoryQuery $query = null)
{
$converter = $this->getConverter($table, $name, 'retrieve');
$converter = $this->getConverter($table, $name, 'retrieve', $query);
if ($converter !== null) {
$value = $this->$converter($value);
}
@ -529,15 +535,17 @@ abstract class Repository implements Selectable
/**
* Return the name of the conversion method for the given alias or column name and context
*
* @param string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve
* @param string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve
* @param RepositoryQuery $query An optional query to pass as context
* (unused by the base implementation)
*
* @return string
*
* @throws ProgrammingError In case a conversion rule is found but not any conversion method
*/
protected function getConverter($table, $name, $context)
protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
{
$conversionRules = $this->getConversionRules();
if (! isset($conversionRules[$table])) {

View File

@ -153,7 +153,7 @@ class RepositoryQuery implements QueryInterface, Iterator
{
$this->query->where(
$this->repository->requireFilterColumn($this->target, $column, $this),
$this->repository->persistColumn($this->target, $column, $value)
$this->repository->persistColumn($this->target, $column, $value, $this)
);
return $this;
}
@ -401,7 +401,7 @@ class RepositoryQuery implements QueryInterface, Iterator
if ($result !== false && $this->repository->providesValueConversion($this->target)) {
$columns = $this->getColumns();
$column = isset($columns[0]) ? $columns[0] : key($columns);
return $this->repository->retrieveColumn($this->target, $column, $result);
return $this->repository->retrieveColumn($this->target, $column, $result, $this);
}
return $result;
@ -425,7 +425,7 @@ class RepositoryQuery implements QueryInterface, Iterator
$alias = $column;
}
$result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias);
$result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias, $this);
}
}
@ -450,7 +450,7 @@ class RepositoryQuery implements QueryInterface, Iterator
$column = is_int($aliases[0]) ? $columns[0] : $aliases[0];
if ($this->repository->providesValueConversion($this->target, $column)) {
foreach ($results as & $value) {
$value = $this->repository->retrieveColumn($this->target, $column, $value);
$value = $this->repository->retrieveColumn($this->target, $column, $value, $this);
}
}
}
@ -483,8 +483,13 @@ class RepositoryQuery implements QueryInterface, Iterator
) {
$newResults = array();
foreach ($results as $colOneValue => $colTwoValue) {
$colOneValue = $this->repository->retrieveColumn($this->target, $colOne, $colOneValue);
$newResults[$colOneValue] = $this->repository->retrieveColumn($this->target, $colTwo, $colTwoValue);
$colOneValue = $this->repository->retrieveColumn($this->target, $colOne, $colOneValue, $this);
$newResults[$colOneValue] = $this->repository->retrieveColumn(
$this->target,
$colTwo,
$colTwoValue,
$this
);
}
$results = $newResults;
@ -516,7 +521,7 @@ class RepositoryQuery implements QueryInterface, Iterator
$alias = $column;
}
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias);
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
}
foreach (($this->getOrder() ?: array()) as $rule) {
@ -591,7 +596,7 @@ class RepositoryQuery implements QueryInterface, Iterator
$alias = $column;
}
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias);
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
}
}

View File

@ -26,7 +26,8 @@ class JavaScript
'js/icinga/behavior/sparkline.js',
'js/icinga/behavior/tristate.js',
'js/icinga/behavior/navigation.js',
'js/icinga/behavior/form.js'
'js/icinga/behavior/form.js',
'js/icinga/behavior/actiontable.js'
);
protected static $vendorFiles = array(

View File

@ -8,7 +8,7 @@ use Icinga\Module\Monitoring\Forms\Command\CommandForm;
use Icinga\Web\Notification;
/**
* Form for enabling or disabling features of Icinga objects, i.e. hosts or services
* Form for enabling or disabling features of Icinga instances
*/
class ToggleInstanceFeaturesCommandForm extends CommandForm
{
@ -79,65 +79,68 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
} else {
$notificationDescription = null;
}
$toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : '';
$this->addElements(array(
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS,
array(
'label' => $this->translate('Active Host Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
'label' => $this->translate('Active Host Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS,
array(
'label' => $this->translate('Active Service Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
'label' => $this->translate('Active Service Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS,
array(
'label' => $this->translate('Event Handlers'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
'label' => $this->translate('Event Handlers'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION,
array(
'label' => $this->translate('Flap Detection'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
'label' => $this->translate('Flap Detection'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS,
array(
'label' => $this->translate('Notifications'),
'autosubmit' => true,
'description' => $notificationDescription,
'decorators' => array(
'ViewHelper',
'Errors',
array(
'Description',
array('tag' => 'span', 'class' => 'description', 'escape' => false)
),
'Label',
array('HtmlTag', array('tag' => 'div'))
'label' => $this->translate('Notifications'),
'autosubmit' => true,
'description' => $notificationDescription,
'decorators' => array(
'ViewHelper',
'Errors',
array(
'Description',
array('tag' => 'span', 'class' => 'description', 'escape' => false)
),
'disabled' => $toggleDisabled
)
),
array(
'Label',
array('HtmlTag', array('tag' => 'div'))
),
'disabled' => $toggleDisabled
)
);
if (! preg_match('~^v2\.\d+\.\d+.*$~', $this->status->program_version)) {
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING,
array(
@ -145,8 +148,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING,
array(
@ -154,8 +157,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS,
array(
@ -163,8 +166,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
);
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS,
array(
@ -172,18 +175,18 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
);
}
$this->addElement(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA,
array(
'checkbox',
ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA,
array(
'label' => $this->translate('Performance Data'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
'label' => $this->translate('Performance Data'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
));
return $this;
);
}
/**
@ -199,6 +202,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
foreach ($this->getValues() as $feature => $enabled) {
$this->getElement($feature)->setChecked($instanceStatus->{$feature});
}
return $this;
}
@ -254,18 +258,19 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
);
foreach ($this->getValues() as $feature => $enabled) {
$toggleFeature = new ToggleInstanceFeatureCommand();
$toggleFeature
->setFeature($feature)
->setEnabled($enabled);
$this->getTransport($this->request)->send($toggleFeature);
if ((bool) $this->status->{$feature} !== (bool) $enabled) {
$toggleFeature = new ToggleInstanceFeatureCommand();
$toggleFeature
->setFeature($feature)
->setEnabled($enabled);
$this->getTransport($this->request)->send($toggleFeature);
Notification::success(
$notifications[$feature][$enabled ? 0 : 1]
);
}
}
return true;
}
}

View File

@ -72,7 +72,7 @@ class DeleteCommentCommandForm extends CommandForm
'escape' => false,
'type' => 'submit',
'class' => 'link-like',
'label' => $this->getView()->icon('cancel'),
'label' => $this->getView()->icon('trash'),
'title' => $this->translate('Delete this comment'),
'decorators' => array('ViewHelper')
)

View File

@ -31,26 +31,28 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
public function createElements(array $formData = array())
{
$toggleDisabled = $this->hasPermission('monitoring/command/feature/object') ? null : '';
$this->addElements(array(
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS,
array(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS,
array(
'label' => $this->translate('Active Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS,
array(
'label' => $this->translate('Passive Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
'label' => $this->translate('Active Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS,
array(
'label' => $this->translate('Passive Checks'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
if (! preg_match('~^v2\.\d+\.\d+.*$~', $this->getIcingaVersion())) {
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_OBSESSING,
array(
@ -58,36 +60,36 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
);
}
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS,
array(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS,
array(
'label' => $this->translate('Notifications'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER,
array(
'label' => $this->translate('Event Handler'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
),
array(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION,
array(
'label' => $this->translate('Flap Detection'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
'label' => $this->translate('Notifications'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
));
return $this;
);
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER,
array(
'label' => $this->translate('Event Handler'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
$this->addElement(
'checkbox',
ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION,
array(
'label' => $this->translate('Flap Detection'),
'autosubmit' => true,
'disabled' => $toggleDisabled
)
);
}
/**
@ -107,6 +109,7 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
$element->setDescription($this->translate('changed'));
}
}
return $this;
}
@ -162,6 +165,17 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
}
}
}
return true;
}
/**
* Fetch and return the program version of the current instance
*
* @return string
*/
protected function getIcingaVersion()
{
return $this->getBackend()->select()->from('programstatus', array('program_version'))->fetchOne();
}
}

View File

@ -377,7 +377,7 @@ class BackendConfigForm extends ConfigForm
$rowCount = $db->select()->from('icinga_instances')->count();
if ($rowCount === 0) {
$form->error($form->translate(
$form->warning($form->translate(
'There is currently no icinga instance writing to the IDO. Make sure '
. 'that a icinga instance is configured and able to write to the IDO.'
));

View File

@ -20,8 +20,13 @@ class InstancePage extends Form
public function createElements(array $formData)
{
$instanceConfigForm = new InstanceConfigForm();
$instanceConfigForm->createElements($formData);
$this->addElements($instanceConfigForm->getElements());
$this->getElement('name')->setValue('icinga');
$this->addSubForm($instanceConfigForm, 'instance_form');
$instanceConfigForm->create($formData);
$instanceConfigForm->getElement('name')->setValue('icinga');
}
public function getValues($suppressArrayNotation = false)
{
return $this->getSubForm('instance_form')->getValues($suppressArrayNotation);
}
}

View File

@ -34,7 +34,7 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
$linkText,
'monitoring/host/show',
array('host' => $host),
array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $host))
array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $linkText))
);
}
@ -45,10 +45,11 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
* @param string $serviceLinkText Text for the service link, e.g. the service's display name
* @param string $host Hostname
* @param string $hostLinkText Text for the host link, e.g. the host's display name
* @param string $class An optional class to use for this link
*
* @return string
*/
public function service($service, $serviceLinkText, $host, $hostLinkText)
public function service($service, $serviceLinkText, $host, $hostLinkText, $class = null)
{
return sprintf(
'%s: %s',
@ -57,11 +58,14 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
$serviceLinkText,
'monitoring/service/show',
array('host' => $host, 'service' => $service),
array('title' => sprintf(
$this->view->translate('Show detailed information for service %s on host %s'),
$service,
$host
))
array(
'title' => sprintf(
$this->view->translate('Show detailed information for service %s on host %s'),
$serviceLinkText,
$hostLinkText
),
'class' => $class
)
)
);
}

View File

@ -22,7 +22,7 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
$pieChartData = PerfdataSet::fromString($perfdataStr)->asArray();
uasort(
$pieChartData,
function($a, $b) {
function ($a, $b) {
return $a->worseThan($b) ? -1 : ($b->worseThan($a) ? 1 : 0);
}
);

View File

@ -27,24 +27,29 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
'@@@@@@',
);
public function pluginOutput($output)
public function pluginOutput($output, $raw = false)
{
if (empty($output)) {
return '';
}
$output = preg_replace('~<br[^>]+>~', "\n", $output);
if (preg_match('~<\w+[^>^\\\]{,60}>~', $output)) {
$output = preg_replace('~<br[^>]*>~', "\n", $output);
if (strlen($output) > strlen(strip_tags($output))) {
// HTML
$output = preg_replace('~<table~', '<table style="font-size: 0.75em"',
$output = preg_replace(
'~<table~',
'<table style="font-size: 0.75em"',
$this->getPurifier()->purify($output)
);
} else {
// Plaintext
$output = '<pre class="pluginoutput">' . preg_replace(
$output = preg_replace(
self::$txtPatterns,
self::$txtReplacements,
$this->view->escape($output)
) . '</pre>';
);
}
if (! $raw) {
$output = '<pre class="pluginoutput">' . $output . '</pre>';
}
$output = $this->fixLinks($output);
return $output;

View File

@ -8,7 +8,7 @@
</p>
</div>
<div class="content multi-commands">
<h3><?= $this->icon('reschedule') ?> <?= $this->translate('Commands') ?> </h3>
<h3><?= $this->translate('Commands') ?> </h3>
<?= $this->qlink(
sprintf(
$this->translate('Remove all %d scheduled downtimes'),
@ -17,8 +17,7 @@
$removeAllLink,
null,
array(
'icon' => 'trash',
'title' => $this->translate('Remove all selected downtimes.')
'icon' => 'trash'
)
) ?>
</div>

View File

@ -24,41 +24,38 @@ if (count($comments) === 0) {
data-icinga-multiselect-url="/icingaweb2/monitoring/comments/show"
data-icinga-multiselect-data="comment_id">
<tbody>
<?php foreach ($comments as $comment):
$this->comment = $comment; ?>
<?php foreach ($comments as $comment): ?>
<tr class="state invalid">
<td class="state" style="width: 12em;">
<?= $this->render('partials/comment/comment-description.phtml'); ?>
<?= $this->partial('partials/comment/comment-description.phtml', array('comment' => $comment)); ?>
</td>
<td>
<?php if ($comment->objecttype === 'service'): ?>
<?= $this->icon('service', $this->translate('Service')); ?>
<?= $this->qlink(
sprintf(
'%s: %s',
$comment->host_display_name,
$comment->service_display_name
),
<?= $this->icon('service', $this->translate('Service')); ?> <?= $this->qlink(
$this->escape($comment->host_display_name) . ': ' . $this->escape($comment->service_display_name),
'monitoring/comment/show',
array('comment_id' => $comment->id),
array('title' => sprintf(
$this->translate('Show detailed information for comment on %s for %s'),
array(
'title' => sprintf(
$this->translate('Show detailed information for this comment about service %s on host %s'),
$comment->service_display_name,
$comment->host_display_name
))) ?>
),
'class' => 'rowaction'
)
); ?>
<?php else: ?>
<?= $this->icon('host', $this->translate('Host')); ?>
<?= $this->qlink(
$comment->host_display_name,
<?= $this->icon('host', $this->translate('Host')); ?> <?= $this->qlink(
$this->escape($comment->host_display_name),
'monitoring/comment/show',
array('comment_id' => $comment->id),
array('title' => sprintf(
$this->translate('Show detailed information for comment on %s'),
array(
'title' => sprintf(
$this->translate('Show detailed information for this comment about host %s'),
$comment->host_display_name
))) ?>
)
)
); ?>
<?php endif ?>
<br>
<?= $this->icon('comment', $this->translate('Comment')); ?> <?= isset($comment->author)

View File

@ -46,22 +46,34 @@ if (count($downtimes) === 0) {
<?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?>
</td>
<td>
<?php
if ($isService) {
echo $this->icon('service');
} else {
echo $this->icon('host');
}
?>
<?= $this->qlink(
sprintf('%s: %s', $downtime->host_display_name, $downtime->service_display_name),
'monitoring/downtime/show',
array('downtime_id' => $downtime->id),
array('title' => sprintf(
$this->translate('Show detailed information for downtime on %s for %s'),
<?php if ($isService): ?>
<?= $this->icon('service', $this->translate('Service')); ?> <?= $this->qlink(
$this->escape($downtime->host_display_name) . ': ' . $this->escape($downtime->service_display_name),
'monitoring/downtime/show',
array('downtime_id' => $downtime->id),
array(
'title' => sprintf(
$this->translate('Show detailed information for this downtime scheduled for service %s on host %s'),
$downtime->service_display_name,
$downtime->host_display_name
))) ?>
),
'class' => 'rowaction'
)
); ?>
<?php else: ?>
<?= $this->icon('host', $this->translate('host')); ?> <?= $this->qlink(
$this->escape($downtime->host_display_name),
'monitoring/downtime/show',
array('downtime_id' => $downtime->id),
array(
'title' => sprintf(
$this->translate('Show detailed information for this downtime scheduled for host %s'),
$downtime->host_display_name
),
'class' => 'rowaction'
)
); ?>
<?php endif ?>
<br>
<?= $this->icon('comment', $this->translate('Comment')); ?> [<?= $this->escape($downtime->author_name) ?>] <?= $this->escape($downtime->comment) ?>
<br>

View File

@ -82,7 +82,7 @@ if (count($history) === 0) {
<td>
<?php if ($isService): ?>
<?= $this->link()->service(
$event->service_description, $event->service_display_name, $event->host_name, $event->host_display_name
$event->service_description, $event->service_display_name, $event->host_name, $event->host_display_name, 'rowaction'
) ?>
<?php else: ?>
<?= $this->link()->host($event->host_name, $event->host_display_name) ?>

View File

@ -60,7 +60,8 @@ if (count($hosts) === 0) {
$hostLink,
null,
array(
'title' => sprintf($this->translate('Show detailed information for host %s'), $host->host_display_name)
'title' => sprintf($this->translate('Show detailed information for host %s'), $host->host_display_name),
'class' => 'rowaction'
)
); ?>
<?php if (isset($host->host_unhandled_services) && $host->host_unhandled_services > 0): ?>
@ -89,7 +90,7 @@ if (count($hosts) === 0) {
)
) ?>)</span>
<?php endif ?>
<p class="pluginoutput"><?= $this->escape($this->ellipsis($host->host_output, 10000)) ?></p>
<p class="pluginoutput"><?= $this->pluginOutput($this->ellipsis($host->host_output, 10000), true) ?></p>
</td>
<?php foreach($this->addColumns as $col): ?>
<td><?= $this->escape($host->$col) ?></td>

View File

@ -48,7 +48,7 @@ if (count($notifications) === 0) {
<?= $this->link()->host($notification->host_name, $notification->host_display_name) ?>
<?php endif ?>
<br>
<?= $this->escape($this->ellipsis($notification->notification_output, 10000)) ?>
<?= $this->pluginOutput($this->ellipsis($notification->notification_output, 10000), true) ?>
<br>
<?php if (! $this->contact): ?>
<small>

View File

@ -82,7 +82,7 @@ if (count($services) === 0) {
)
) ?><br />
<div class="sparkline-box"><?= $this->perfdata($service->service_perfdata, true, 5) ?> </div>
<p class="pluginoutput"><?= $this->escape($this->ellipsis($service->service_output, 10000)) ?></p>
<p class="pluginoutput"><?= $this->pluginOutput($this->ellipsis($service->service_output, 10000), true) ?></p>
</td>
<?php foreach($this->addColumns as $col): ?>
<td><?= $this->escape($service->$col) ?></td>

View File

@ -1,8 +1,8 @@
<table class="action">
<table class="action" data-base-target="_next">
<tbody>
<?php $i = 0; foreach ($downtimes as $downtime):
if (++ $i > 5) {
continue;
break;
} ?>
<tr class="state <?= $downtime->stateText ?>">
<td class="state">
@ -13,16 +13,21 @@
<td class="name oneline">
<?php if ($downtime->isService): ?>
<?= $this->icon('service', $this->translate('Service')) ?>
<b><?= $downtime->service ?> on <?= $downtime->host_name ?>.</b>
<?= $this->link()->service(
$downtime->service_description,
$downtime->service_display_name,
$downtime->host_name,
$downtime->host_display_name
); ?>
<?php else: ?>
<?= $this->icon('host', $this->translate('Host')) ?>
<b><?= $downtime->host_name ?>.</b>
<?= $this->link()->host($downtime->host_name, $downtime->host_display_name); ?>
<?php endif; ?>
<br>
<?php if ($downtime->is_flexible): ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->isService
$downtime->isService
? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.')
: $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'),
$this->formatDate($downtime->start),
@ -33,7 +38,7 @@
); ?>
<?php else: ?>
<?= sprintf(
$this->isService
$downtime->isService
? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.')
: $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'),
$this->formatDateTime($downtime->scheduled_start),
@ -44,7 +49,7 @@
<?php else: ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->isService
$downtime->isService
? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.')
: $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'),
$this->formatDate($downtime->start),
@ -54,13 +59,13 @@
); ?>
<?php else: ?>
<?= sprintf(
$this->isService
$downtime->isService
? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.')
: $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
$this->formatDate($downtime->start),
$this->formatTime($downtime->start),
$this->formatDate($downtime->end),
$this->formatTime($downtime->end)
$this->formatDate($downtime->scheduled_start),
$this->formatTime($downtime->scheduled_start),
$this->formatDate($downtime->scheduled_end),
$this->formatTime($downtime->scheduled_end)
); ?>
<?php endif ?>
<?php endif ?>
@ -68,18 +73,18 @@
</tr>
<?php endforeach; ?>
</tbody>
</table>
</table>
<p>
<?php if ($i > 5): ?>
<?php if (count($downtimes) > 5): ?>
<p>
<?= $this->qlink(
sprintf($this->translate('show all %d downtimes'), $i),
sprintf($this->translate('List all %d downtimes'), $i),
$listAllLink,
null,
array(
'icon' => $i > 5 ? 'down-open' : '',
'data-base-target' => "_next"
'icon' => 'down-open',
'data-base-target' => "_next"
)
) ?>
<?php endif ?>
</p>
</p>
<?php endif ?>

View File

@ -136,28 +136,35 @@ class BackendStep extends Step
public function getReport()
{
$report = '';
$report = array();
if ($this->backendIniError === false) {
$message = mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s');
$report .= '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/backends.ini')) . '</p>';
} elseif ($this->backendIniError !== null) {
$message = mt(
'monitoring',
'Monitoring backend configuration could not be written to: %s; An error occured:'
);
$report .= '<p class="error">' . sprintf(
$message,
$report[] = sprintf(
mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s'),
Config::resolvePath('modules/monitoring/backends.ini')
) . '</p><p>' . $this->backendIniError->getMessage() . '</p>';
);
} elseif ($this->backendIniError !== null) {
$report[] = sprintf(
mt(
'monitoring',
'Monitoring backend configuration could not be written to: %s. An error occured:'
),
Config::resolvePath('modules/monitoring/backends.ini')
);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->backendIniError->getMessage());
}
if ($this->resourcesIniError === false) {
$message = mt('monitoring', 'Resource configuration has been successfully updated: %s');
$report .= '<p>' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>';
$report[] = sprintf(
mt('monitoring', 'Resource configuration has been successfully updated: %s'),
Config::resolvePath('resources.ini')
);
} elseif ($this->resourcesIniError !== null) {
$message = mt('monitoring', 'Resource configuration could not be udpated: %s; An error occured:');
$report .= '<p class="error">' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>'
. '<p>' . $this->resourcesIniError->getMessage() . '</p>';
$report[] = sprintf(
mt('monitoring', 'Resource configuration could not be udpated: %s. An error occured:'),
Config::resolvePath('resources.ini')
);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->resourcesIniError->getMessage());
}
return $report;

View File

@ -85,15 +85,21 @@ class InstanceStep extends Step
public function getReport()
{
if ($this->error === false) {
$message = mt('monitoring', 'Monitoring instance configuration has been successfully created: %s');
return '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini')) . '</p>';
return array(sprintf(
mt('monitoring', 'Monitoring instance configuration has been successfully created: %s'),
Config::resolvePath('modules/monitoring/instances.ini')
));
} elseif ($this->error !== null) {
$message = mt(
'monitoring',
'Monitoring instance configuration could not be written to: %s; An error occured:'
return array(
sprintf(
mt(
'monitoring',
'Monitoring instance configuration could not be written to: %s. An error occured:'
),
Config::resolvePath('modules/monitoring/instances.ini')
),
sprintf(mt('setup', 'ERROR: %s'), $this->error->getMessage())
);
return '<p class="error">' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini'))
. '</p><p>' . $this->error->getMessage() . '</p>';
}
}
}

View File

@ -25,7 +25,7 @@ use Icinga\Module\Setup\Requirement\PhpModuleRequirement;
class MonitoringWizard extends Wizard implements SetupWizard
{
/**
* @see Wizard::init()
* Register all pages for this wizard
*/
public function init()
{
@ -39,7 +39,10 @@ class MonitoringWizard extends Wizard implements SetupWizard
}
/**
* @see Wizard::setupPage()
* Setup the given page that is either going to be displayed or validated
*
* @param Form $page The page to setup
* @param Request $request The current request
*/
public function setupPage(Form $page, Request $request)
{
@ -52,18 +55,30 @@ class MonitoringWizard extends Wizard implements SetupWizard
$this->getDirection() === static::FORWARD
&& ($page->getName() === 'setup_monitoring_ido' || $page->getName() === 'setup_monitoring_livestatus')
) {
if ((($dbResourceData = $this->getPageData('setup_db_resource')) !== null
&& $dbResourceData['name'] === $request->getPost('name'))
|| (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null
&& $ldapResourceData['name'] === $request->getPost('name'))
if (
(($authDbResourceData = $this->getPageData('setup_auth_db_resource')) !== null
&& $authDbResourceData['name'] === $request->getPost('name'))
|| (($configDbResourceData = $this->getPageData('setup_config_db_resource')) !== null
&& $configDbResourceData['name'] === $request->getPost('name'))
|| (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null
&& $ldapResourceData['name'] === $request->getPost('name'))
) {
$page->addError(mt('monitoring', 'The given resource name is already in use.'));
$page->error(mt('monitoring', 'The given resource name is already in use.'));
}
}
}
/**
* @see Wizard::getNewPage()
* Return the new page to set as current page
*
* {@inheritdoc} Runs additional checks related to some registered pages.
*
* @param string $requestedPage The name of the requested page
* @param Form $originPage The origin page
*
* @return Form The new page
*
* @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
*/
protected function getNewPage($requestedPage, Form $originPage)
{
@ -81,7 +96,9 @@ class MonitoringWizard extends Wizard implements SetupWizard
}
/**
* @see Wizard::addButtons()
* Add buttons to the given page based on its position in the page-chain
*
* @param Form $page The page to add the buttons to
*/
protected function addButtons(Form $page)
{
@ -100,7 +117,9 @@ class MonitoringWizard extends Wizard implements SetupWizard
}
/**
* @see SetupWizard::getSetup()
* Return the setup for this wizard
*
* @return Setup
*/
public function getSetup()
{
@ -132,7 +151,9 @@ class MonitoringWizard extends Wizard implements SetupWizard
}
/**
* @see SetupWizard::getRequirements()
* Return the requirements of this wizard
*
* @return RequirementSet
*/
public function getRequirements()
{

View File

@ -124,9 +124,14 @@ abstract class ObjectList implements Countable, IteratorAggregate, Filterable
public function count()
{
if ($this->count === null) {
$this->count = (int) $this->backend->select()->from($this->dataViewName)->applyFilter($this->filter)
->getQuery()->count();
$this->count = (int) $this->backend
->select()
->from($this->dataViewName, $this->columns)
->applyFilter($this->filter)
->getQuery()
->count();
}
return $this->count;
}

View File

@ -63,15 +63,21 @@ class SecurityStep extends Step
public function getReport()
{
if ($this->error === false) {
$message = mt('monitoring', 'Monitoring security configuration has been successfully created: %s');
return '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini')) . '</p>';
return array(sprintf(
mt('monitoring', 'Monitoring security configuration has been successfully created: %s'),
Config::resolvePath('modules/monitoring/config.ini')
));
} elseif ($this->error !== null) {
$message = mt(
'monitoring',
'Monitoring security configuration could not be written to: %s; An error occured:'
return array(
sprintf(
mt(
'monitoring',
'Monitoring security configuration could not be written to: %s. An error occured:'
),
Config::resolvePath('modules/monitoring/config.ini')
),
sprintf(mt('setup', 'ERROR: %s'), $this->error->getMessage())
);
return '<p class="error">' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini'))
. '</p><p>' . $this->error->getMessage() . '</p>';
}
}
}

View File

@ -76,6 +76,7 @@ abstract class MonitoredObjectController extends Controller
$this->object->populate();
$toggleFeaturesForm = new ToggleObjectFeaturesCommandForm();
$toggleFeaturesForm
->setBackend($this->backend)
->load($this->object)
->setObjects($this->object)
->handleRequest();

View File

@ -165,21 +165,25 @@ class AdminAccountPage extends Form
'password',
'new_user_password',
array(
'required' => true,
'label' => $this->translate('Password'),
'description' => $this->translate('Enter the password to assign to the newly created account.')
'required' => true,
'renderPassword' => true,
'label' => $this->translate('Password'),
'description' => $this->translate(
'Enter the password to assign to the newly created account.'
)
)
);
$this->addElement(
'password',
'new_user_2ndpass',
array(
'required' => true,
'label' => $this->translate('Repeat password'),
'description' => $this->translate(
'required' => true,
'renderPassword' => true,
'label' => $this->translate('Repeat password'),
'description' => $this->translate(
'Please repeat the password given above to avoid typing errors.'
),
'validators' => array(
'validators' => array(
array('identical', false, array('new_user_password'))
)
)
@ -200,7 +204,7 @@ class AdminAccountPage extends Form
return false;
}
if ($data['user_type'] === 'new_user' && !$this->hasUser($data['new_user'])) {
if ($data['user_type'] === 'new_user' && $this->hasUser($data['new_user'])) {
$this->getElement('new_user')->addError($this->translate('Username already exists.'));
return false;
}
@ -253,7 +257,11 @@ class AdminAccountPage extends Form
*/
protected function hasUser($username)
{
return $this->createBackend()->select()->where('user_name', $username)->count() > 1;
try {
return $this->createBackend()->select()->where('user_name', $username)->count() > 1;
} catch (Exception $_) {
return null;
}
}
/**

View File

@ -121,7 +121,7 @@ class AuthBackendPage extends Form
return false;
}
if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
if ($this->config['type'] === 'ldap' && ( !isset($data['skip_validation']) || $data['skip_validation'] == 0)) {
$self = clone $this;
$self->addElement(
'text',
@ -130,7 +130,7 @@ class AuthBackendPage extends Form
'value' => $this->getResourceConfig()
)
);
if ($this->config['type'] === 'ldap' && false === LdapBackendForm::isValidUserBackend($self)) {
if (! LdapBackendForm::isValidUserBackend($self)) {
$this->addSkipValidationCheckbox();
return false;
}

View File

@ -38,7 +38,6 @@ class DatabaseCreationPage extends Form
*/
public function init()
{
$this->setName('setup_database_creation');
$this->setTitle($this->translate('Database Setup', 'setup.page.title'));
$this->addDescription($this->translate(
'It seems that either the database you defined earlier does not yet exist and cannot be created'
@ -108,8 +107,9 @@ class DatabaseCreationPage extends Form
'password',
'password',
array(
'label' => $this->translate('Password'),
'description' => $this->translate('The password for the database user defined above')
'renderPassword' => true,
'label' => $this->translate('Password'),
'description' => $this->translate('The password for the database user defined above')
)
);
@ -156,7 +156,7 @@ class DatabaseCreationPage extends Form
$db->connectToHost(); // Are we able to login on the server?
} catch (PDOException $e) {
// We are NOT able to login on the server..
$this->addError($e->getMessage());
$this->error($e->getMessage());
$this->addSkipValidationCheckbox();
return false;
}
@ -165,7 +165,7 @@ class DatabaseCreationPage extends Form
// In case we are connected the credentials filled into this
// form need to be granted to create databases, users...
if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) {
$this->addError(
$this->error(
$this->translate('The provided credentials cannot be used to create the database and/or the user.')
);
$this->addSkipValidationCheckbox();
@ -174,7 +174,7 @@ class DatabaseCreationPage extends Form
// ...and to grant all required usage privileges to others
if (false === $db->isGrantable($this->databaseUsagePrivileges)) {
$this->addError(sprintf(
$this->error(sprintf(
$this->translate(
'The provided credentials cannot be used to grant all required privileges to the login "%s".'
),

View File

@ -18,7 +18,6 @@ class DbResourcePage extends Form
*/
public function init()
{
$this->setName('setup_db_resource');
$this->setTitle($this->translate('Database Resource', 'setup.page.title'));
$this->addDescription($this->translate(
'Now please configure your database resource. Note that the database itself does not need to'
@ -56,14 +55,6 @@ class DbResourcePage extends Form
$resourceForm = new DbResourceForm();
$this->addElements($resourceForm->createElements($formData)->getElements());
$this->getElement('name')->setValue('icingaweb_db');
$this->addElement(
'hidden',
'prefix',
array(
'required' => true,
'value' => 'icingaweb_'
)
);
}
/**
@ -84,7 +75,7 @@ class DbResourcePage extends Form
$db = new DbTool($this->getValues());
$db->checkConnectivity();
} catch (PDOException $e) {
$this->addError($e->getMessage());
$this->error($e->getMessage());
$this->addSkipValidationCheckbox();
return false;
}

View File

@ -3,8 +3,9 @@
namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form;
use Icinga\Forms\Config\General\ApplicationConfigForm;
use Icinga\Forms\Config\General\LoggingConfigForm;
use Icinga\Web\Form;
/**
* Wizard page to define the application and logging configuration
@ -28,7 +29,13 @@ class GeneralConfigPage extends Form
*/
public function createElements(array $formData)
{
$loggingForm = new LoggingConfigForm();
$this->addElements($loggingForm->createElements($formData)->getElements());
$appConfigForm = new ApplicationConfigForm();
$appConfigForm->createElements($formData);
$appConfigForm->removeElement('global_module_path');
$appConfigForm->removeElement('global_config_resource');
$this->addElements($appConfigForm->getElements());
$loggingConfigForm = new LoggingConfigForm();
$this->addElements($loggingConfigForm->createElements($formData)->getElements());
}
}

View File

@ -77,7 +77,7 @@ class LdapDiscoveryPage extends Form
} catch (Exception $e) {
}
$this->addError(
$this->error(
sprintf($this->translate('Could not find any LDAP servers on the domain "%s".'), $data['domain'])
);
} else {

View File

@ -1,47 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form;
use Icinga\Application\Platform;
/**
* Wizard page to choose a preference backend
*/
class PreferencesPage extends Form
{
/**
* Initialize this page
*/
public function init()
{
$this->setRequiredCue(null);
$this->setName('setup_preferences_type');
$this->setTitle($this->translate('Preferences', 'setup.page.title'));
$this->addDescription($this->translate('Please choose how Icinga Web 2 should store user preferences.'));
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$storageTypes = array();
$storageTypes['ini'] = $this->translate('File System (INI Files)');
if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) {
$storageTypes['db'] = $this->translate('Database');
}
$storageTypes['none'] = $this->translate('Don\'t Store Preferences');
$this->addElement(
'select',
'store',
array(
'required' => true,
'label' => $this->translate('User Preference Storage Type'),
'multiOptions' => $storageTypes
)
);
}
}

View File

@ -1,22 +1,10 @@
<div id="setup-finish">
<div class="report">
<?php $firstLine = true; ?>
<?php foreach ($report as $entry): ?>
<?php if ($entry): ?>
<?php if (false === $firstLine): ?>
<div class="line-separator"></div>
<?php endif ?>
<?= $entry; ?>
<?php $firstLine = false; ?>
<?php endif ?>
<?php endforeach ?>
<?php if ($success): ?>
<p class="success"><?= $this->translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?></p>
<?php else: ?>
<p class="failure"><?= $this->translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?></p>
<?php endif ?>
</div>
<div class="buttons">
<?php if ($success): ?>
<h2 class="success"><?= $this->translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?></h2>
<?php else: ?>
<h2 class="failure"><?= $this->translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?></h2>
<?php endif ?>
<div class="buttons pull-right">
<?php if ($success): ?>
<?= $this->qlink(
$this->translate('Login to Icinga Web 2'),
@ -39,4 +27,7 @@
); ?>
<?php endif ?>
</div>
<textarea class="report" readonly><?= join("\n\n", array_map(function($a) {
return join("\n", $a);
}, $report)); ?></textarea>
</div>

View File

@ -9,53 +9,34 @@ class RequirementsRenderer extends RecursiveIteratorIterator
{
public function beginIteration()
{
$this->tags[] = '<table class="requirements">';
$this->tags[] = '<tbody>';
$this->tags[] = '<ul class="requirements">';
}
public function endIteration()
{
$this->tags[] = '</tbody>';
$this->tags[] = '</table>';
$this->tags[] = '</ul>';
}
public function beginChildren()
{
$this->tags[] = '<tr>';
$this->tags[] = '<li>';
$currentSet = $this->getSubIterator();
$state = $currentSet->getState() ? 'fulfilled' : (
$currentSet->isOptional() ? 'not-available' : 'missing'
);
$colSpanRequired = $this->hasSingleRequirements($this->getSubIterator($this->getDepth() - 1));
$this->tags[] = '<td class="set-state ' . $state . '"' . ($colSpanRequired ? ' colspan=3' : '') . '>';
$this->beginIteration();
$state = $currentSet->getState() ? 'fulfilled' : ($currentSet->isOptional() ? 'not-available' : 'missing');
$this->tags[] = '<ul class="set-state ' . $state . '">';
}
public function endChildren()
{
$this->endIteration();
$this->tags[] = '</td>';
$this->tags[] = '</tr>';
}
protected function hasSingleRequirements(RequirementSet $requirements)
{
$set = $requirements->getAll();
foreach ($set as $entry) {
if ($entry instanceof Requirement) {
return true;
}
}
return false;
$this->tags[] = '</ul>';
$this->tags[] = '</li>';
}
public function render()
{
foreach ($this as $requirement) {
$this->tags[] = '<tr>';
$this->tags[] = '<td class="title"><h2>' . $requirement->getTitle() . '</h2></td>';
$this->tags[] = '<td class="desc">';
$this->tags[] = '<li class="clearfix">';
$this->tags[] = '<div class="title"><h3>' . $requirement->getTitle() . '</h3></div>';
$this->tags[] = '<div class="description">';
$descriptions = $requirement->getDescriptions();
if (count($descriptions) > 1) {
$this->tags[] = '<ul>';
@ -66,11 +47,11 @@ class RequirementsRenderer extends RecursiveIteratorIterator
} elseif (! empty($descriptions)) {
$this->tags[] = $descriptions[0];
}
$this->tags[] = '</td>';
$this->tags[] = '<td class="state ' . ($requirement->getState() ? 'fulfilled' : (
$this->tags[] = '</div>';
$this->tags[] = '<div class="state ' . ($requirement->getState() ? 'fulfilled' : (
$requirement->isOptional() ? 'not-available' : 'missing'
)) . '">' . $requirement->getStateText() . '</td>';
$this->tags[] = '</tr>';
)) . '">' . $requirement->getStateText() . '</div>';
$this->tags[] = '</li>';
}
return implode("\n", $this->tags);

View File

@ -81,13 +81,16 @@ class Setup implements IteratorAggregate
/**
* Return a report of all actions that were run
*
* @return array An array of HTML strings
* @return array An array of arrays of strings
*/
public function getReport()
{
$reports = array();
foreach ($this->steps as $step) {
$reports[] = $step->getReport();
$report = $step->getReport();
if (! empty($report)) {
$reports[] = $report;
}
}
return $reports;

View File

@ -23,9 +23,9 @@ abstract class Step
abstract public function getSummary();
/**
* Return a HTML representation of this step's configuration changes that were made
* Return a textual summary of all configuration changes made
*
* @return string
* @return array
*/
abstract public function getReport();
}

View File

@ -161,32 +161,45 @@ class AuthenticationStep extends Step
public function getReport()
{
$report = '';
$report = array();
if ($this->authIniError === false) {
$message = mt('setup', 'Authentication configuration has been successfully written to: %s');
$report .= '<p>' . sprintf($message, Config::resolvePath('authentication.ini')) . '</p>';
$report[] = sprintf(
mt('setup', 'Authentication configuration has been successfully written to: %s'),
Config::resolvePath('authentication.ini')
);
} elseif ($this->authIniError !== null) {
$message = mt('setup', 'Authentication configuration could not be written to: %s; An error occured:');
$report .= '<p class="error">' . sprintf($message, Config::resolvePath('authentication.ini')) . '</p>'
. '<p>' . $this->authIniError->getMessage() . '</p>';
$report[] = sprintf(
mt('setup', 'Authentication configuration could not be written to: %s. An error occured:'),
Config::resolvePath('authentication.ini')
);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->authIniError->getMessage());
}
if ($this->dbError === false) {
$message = mt('setup', 'Account "%s" has been successfully created.');
$report .= '<p>' . sprintf($message, $this->data['adminAccountData']['username']) . '</p>';
$report[] = sprintf(
mt('setup', 'Account "%s" has been successfully created.'),
$this->data['adminAccountData']['username']
);
} elseif ($this->dbError !== null) {
$message = mt('setup', 'Unable to create account "%s". An error occured:');
$report .= '<p class="error">' . sprintf($message, $this->data['adminAccountData']['username']) . '</p>'
. '<p>' . $this->dbError->getMessage() . '</p>';
$report[] = sprintf(
mt('setup', 'Unable to create account "%s". An error occured:'),
$this->data['adminAccountData']['username']
);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->dbError->getMessage());
}
if ($this->permIniError === false) {
$message = mt('setup', 'Account "%s" has been successfully defined as initial administrator.');
$report .= '<p>' . sprintf($message, $this->data['adminAccountData']['username']) . '</p>';
$report[] = sprintf(
mt('setup', 'Account "%s" has been successfully defined as initial administrator.'),
$this->data['adminAccountData']['username']
);
} elseif ($this->permIniError !== null) {
$message = mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:');
$report .= '<p class="error">' . sprintf($message, $this->data['adminAccountData']['username']) . '</p>'
. '<p>' . $this->permIniError->getMessage() . '</p>';
$report[] = sprintf(
mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:'),
$this->data['adminAccountData']['username']
);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->permIniError->getMessage());
}
return $report;

View File

@ -247,12 +247,14 @@ class DatabaseStep extends Step
public function getReport()
{
if ($this->error === false) {
return '<p>' . join('</p><p>', $this->messages) . '</p>'
. '<p>' . mt('setup', 'The database has been fully set up!') . '</p>';
$report = $this->messages;
$report[] = mt('setup', 'The database has been fully set up!');
return $report;
} elseif ($this->error !== null) {
$message = mt('setup', 'Failed to fully setup the database. An error occured:');
return '<p>' . join('</p><p>', $this->messages) . '</p>'
. '<p class="error">' . $message . '</p><p>' . $this->error->getMessage() . '</p>';
$report = $this->messages;
$report[] = mt('setup', 'Failed to fully setup the database. An error occured:');
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->error->getMessage());
return $report;
}
}

View File

@ -23,13 +23,12 @@ class GeneralConfigStep extends Step
{
$config = array();
foreach ($this->data['generalConfig'] as $sectionAndPropertyName => $value) {
list($section, $property) = explode('_', $sectionAndPropertyName);
list($section, $property) = explode('_', $sectionAndPropertyName, 2);
$config[$section][$property] = $value;
}
$config['preferences']['store'] = $this->data['preferencesStore'];
if (isset($this->data['preferencesResource'])) {
$config['preferences']['resource'] = $this->data['preferencesResource'];
if ($config['global']['config_backend'] === 'db') {
$config['global']['config_resource'] = $this->data['resourceName'];
}
try {
@ -54,14 +53,10 @@ class GeneralConfigStep extends Step
$generalHtml = ''
. '<ul>'
. '<li>' . sprintf(
$this->data['preferencesStore'] === 'ini' ? sprintf(
$this->data['generalConfig']['global_config_backend'] === 'ini' ? sprintf(
t('Preferences will be stored per user account in INI files at: %s'),
Config::resolvePath('preferences')
) : (
$this->data['preferencesStore'] === 'db' ? t('Preferences will be stored using a database.') : (
t('Preferences will not be persisted across browser sessions.')
)
)
) : t('Preferences will be stored using a database.')
) . '</li>'
. '</ul>';
@ -107,12 +102,18 @@ class GeneralConfigStep extends Step
public function getReport()
{
if ($this->error === false) {
$message = mt('setup', 'General configuration has been successfully written to: %s');
return '<p>' . sprintf($message, Config::resolvePath('config.ini')) . '</p>';
return array(sprintf(
mt('setup', 'General configuration has been successfully written to: %s'),
Config::resolvePath('config.ini')
));
} elseif ($this->error !== null) {
$message = mt('setup', 'General configuration could not be written to: %s; An error occured:');
return '<p class="error">' . sprintf($message, Config::resolvePath('config.ini')) . '</p>'
. '<p>' . $this->error->getMessage() . '</p>';
return array(
sprintf(
mt('setup', 'General configuration could not be written to: %s. An error occured:'),
Config::resolvePath('config.ini')
),
sprintf(mt('setup', 'ERROR: %s'), $this->error->getMessage())
);
}
}
}

View File

@ -133,12 +133,18 @@ class ResourceStep extends Step
public function getReport()
{
if ($this->error === false) {
$message = mt('setup', 'Resource configuration has been successfully written to: %s');
return '<p>' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>';
return array(sprintf(
mt('setup', 'Resource configuration has been successfully written to: %s'),
Config::resolvePath('resources.ini')
));
} elseif ($this->error !== null) {
$message = mt('setup', 'Resource configuration could not be written to: %s; An error occured:');
return '<p class="error">' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>'
. '<p>' . $this->error->getMessage() . '</p>';
return array(
sprintf(
mt('setup', 'Resource configuration could not be written to: %s. An error occured:'),
Config::resolvePath('resources.ini')
),
sprintf(mt('setup', 'ERROR: %s'), $this->error->getMessage())
);
}
}
}

View File

@ -167,15 +167,15 @@ class DbTool
*/
protected function assertHostAccess()
{
if (false === isset($this->config['db'])) {
if (! isset($this->config['db'])) {
throw new ConfigurationError('Can\'t connect to database server of unknown type');
} elseif (false === isset($this->config['host'])) {
} elseif (! isset($this->config['host'])) {
throw new ConfigurationError('Can\'t connect to database server without a hostname or address');
} elseif (false === isset($this->config['port'])) {
} elseif (! isset($this->config['port'])) {
throw new ConfigurationError('Can\'t connect to database server without a port');
} elseif (false === isset($this->config['username'])) {
} elseif (! isset($this->config['username'])) {
throw new ConfigurationError('Can\'t connect to database server without a username');
} elseif (false === isset($this->config['password'])) {
} elseif (! isset($this->config['password'])) {
throw new ConfigurationError('Can\'t connect to database server without a password');
}
}
@ -187,7 +187,7 @@ class DbTool
*/
protected function assertDatabaseAccess()
{
if (false === isset($this->config['dbname'])) {
if (! isset($this->config['dbname'])) {
throw new ConfigurationError('Can\'t connect to database without a valid database name');
}
}
@ -351,6 +351,24 @@ class DbTool
}
}
/**
* Return the given table name with all wildcards being escaped
*
* @param string $tableName
*
* @return string
*
* @throws LogicException In case there is no behaviour implemented for the current PDO driver
*/
public function escapeTableWildcards($tableName)
{
if ($this->config['db'] === 'mysql') {
return str_replace(array('_', '%'), array('\_', '\%'), $tableName);
}
throw new LogicException('Unable to escape table wildcards.');
}
/**
* Return the given value escaped as string
*
@ -481,9 +499,12 @@ class DbTool
{
if ($this->config['db'] === 'mysql') {
list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
$queryString = sprintf(
'GRANT %%s ON %s.%%s TO %s@%s',
$this->quoteIdentifier($this->config['dbname']),
$quotedDbName = $this->quoteIdentifier($this->config['dbname']);
$grant = 'GRANT %s';
$on = ' ON %s.%s';
$to = sprintf(
' TO %s@%s',
$this->quoteIdentifier($username),
str_replace('%', '%%', $this->quoteIdentifier($host))
);
@ -491,36 +512,39 @@ class DbTool
$dbPrivileges = array();
$tablePrivileges = array();
foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) {
if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
$tablePrivileges[] = $privilege;
} elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
$dbPrivileges[] = $privilege;
}
}
if (false === empty($tablePrivileges)) {
if (! empty($tablePrivileges)) {
$tableGrant = sprintf($grant, join(',', $tablePrivileges));
foreach ($context as $table) {
$this->exec(
sprintf($queryString, join(',', $tablePrivileges), $this->quoteIdentifier($table))
);
$this->exec($tableGrant . sprintf($on, $quotedDbName, $this->quoteIdentifier($table)) . $to);
}
}
if (false === empty($dbPrivileges)) {
$this->exec(sprintf($queryString, join(',', $dbPrivileges), '*'));
if (! empty($dbPrivileges)) {
$this->exec(
sprintf($grant, join(',', $dbPrivileges))
. sprintf($on, $this->escapeTableWildcards($quotedDbName), '*')
. $to
);
}
} elseif ($this->config['db'] === 'pgsql') {
$dbPrivileges = array();
$tablePrivileges = array();
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
$tablePrivileges[] = $privilege;
} elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
$dbPrivileges[] = $privilege;
}
}
if (false === empty($dbPrivileges)) {
if (! empty($dbPrivileges)) {
$this->exec(sprintf(
'GRANT %s ON DATABASE %s TO %s',
join(',', $dbPrivileges),
@ -529,7 +553,7 @@ class DbTool
));
}
if (false === empty($tablePrivileges)) {
if (! empty($tablePrivileges)) {
foreach ($context as $table) {
$this->exec(sprintf(
'GRANT %s ON TABLE %s TO %s',
@ -634,7 +658,7 @@ EOD;
$dbPrivileges = array();
$tablePrivileges = array();
foreach ($mysqlPrivileges as $privilege) {
if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
$tablePrivileges[] = $privilege;
}
if ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
@ -643,25 +667,46 @@ EOD;
}
$dbPrivilegesGranted = true;
if (false === empty($dbPrivileges)) {
$query = $this->query(
'SELECT COUNT(*) as matches'
$tablePrivilegesGranted = true;
if (! empty($dbPrivileges)) {
$queryString = 'SELECT COUNT(*) as matches'
. ' FROM information_schema.schema_privileges'
. ' WHERE grantee = :grantee'
. ' AND table_schema = :dbname'
. ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $dbPrivileges)) . ')'
. ($requireGrants ? " AND is_grantable = 'YES'" : ''),
array(':grantee' => $grantee, ':dbname' => $this->config['dbname'])
. ' AND privilege_type IN (%s)'
. ($requireGrants ? " AND is_grantable = 'YES'" : '');
$dbAndTableQuery = $this->query(
sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbPrivileges))),
array(':grantee' => $grantee, ':dbname' => $this->escapeTableWildcards($this->config['dbname']))
);
$dbPrivilegesGranted = (int) $query->fetchObject()->matches === count($dbPrivileges);
$grantedDbAndTablePrivileges = (int) $dbAndTableQuery->fetchObject()->matches;
if ($grantedDbAndTablePrivileges === count($dbPrivileges)) {
$tableExclusivePrivileges = array_diff($tablePrivileges, $dbPrivileges);
if (! empty($tableExclusivePrivileges)) {
$tablePrivileges = $tableExclusivePrivileges;
$tablePrivilegesGranted = false;
}
} else {
$tablePrivilegesGranted = false;
$dbExclusivePrivileges = array_diff($dbPrivileges, $tablePrivileges);
if (! empty($dbExclusivePrivileges)) {
$dbExclusiveQuery = $this->query(
sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbExclusivePrivileges))),
array(
':grantee' => $grantee,
':dbname' => $this->escapeTableWildcards($this->config['dbname'])
)
);
$dbPrivilegesGranted = (int) $dbExclusiveQuery->fetchObject()->matches === count(
$dbExclusivePrivileges
);
}
}
}
$tablePrivilegesGranted = true;
if (
false === empty($tablePrivileges) && (
!$dbPrivilegesGranted || array_intersect($dbPrivileges, $tablePrivileges) != $tablePrivileges
)
) {
if (! $tablePrivilegesGranted && !empty($tablePrivileges)) {
$query = $this->query(
'SELECT COUNT(*) as matches'
. ' FROM information_schema.table_privileges'
@ -715,7 +760,7 @@ EOD;
$dbPrivileges = array();
$tablePrivileges = array();
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
$tablePrivileges[] = $privilege;
}
if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
@ -723,7 +768,8 @@ EOD;
}
}
if (false === empty($dbPrivileges)) {
if (! empty($dbPrivileges)) {
$dbExclusivesGranted = true;
foreach ($dbPrivileges as $dbPrivilege) {
$query = $this->query(
'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted',
@ -733,11 +779,23 @@ EOD;
':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
)
);
$privilegesGranted &= $query->fetchObject()->db_privilege_granted;
if (! $query->fetchObject()->db_privilege_granted) {
$privilegesGranted = false;
if (! in_array($dbPrivilege, $tablePrivileges)) {
$dbExclusivesGranted = false;
}
}
}
if ($privilegesGranted) {
// Do not check privileges twice if they are already granted at database level
$tablePrivileges = array_diff($tablePrivileges, $dbPrivileges);
} elseif ($dbExclusivesGranted) {
$privilegesGranted = true;
}
}
if (false === empty($tablePrivileges)) {
if ($privilegesGranted && !empty($tablePrivileges)) {
foreach (array_intersect($context, $this->listTables()) as $table) {
foreach ($tablePrivileges as $tablePrivilege) {
$query = $this->query(

View File

@ -53,13 +53,13 @@ class EnableModuleStep extends Step
$okMessage = mt('setup', 'Module "%s" has been successfully enabled.');
$failMessage = mt('setup', 'Module "%s" could not be enabled. An error occured:');
$report = '';
$report = array();
foreach ($this->moduleNames as $moduleName) {
if (isset($this->errors[$moduleName])) {
$report .= '<p class="error">' . sprintf($failMessage, $moduleName) . '</p>'
. '<p>' . $this->errors[$moduleName]->getMessage() . '</p>';
$report[] = sprintf($failMessage, $moduleName);
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->errors[$moduleName]->getMessage());
} else {
$report .= '<p>' . sprintf($okMessage, $moduleName) . '</p>';
$report[] = sprintf($okMessage, $moduleName);
}
}

View File

@ -53,14 +53,14 @@ class MakeDirStep extends Step
$okMessage = mt('setup', 'Directory "%s" in "%s" has been successfully created.');
$failMessage = mt('setup', 'Unable to create directory "%s" in "%s". An error occured:');
$report = '';
$report = array();
foreach ($this->paths as $path) {
if (array_key_exists($path, $this->errors)) {
if (is_array($this->errors[$path])) {
$report .= '<p class="error">' . sprintf($failMessage, basename($path), dirname($path)) . '</p>'
. '<p>' . $this->errors[$path]['message'] . '</p>';
$report[] = sprintf($failMessage, basename($path), dirname($path));
$report[] = sprintf(mt('setup', 'ERROR: %s'), $this->errors[$path]['message']);
} else {
$report .= '<p>' . sprintf($okMessage, basename($path), dirname($path)) . '</p>';
$report[] = sprintf($okMessage, basename($path), dirname($path));
}
}
}

View File

@ -13,7 +13,6 @@ use Icinga\Module\Setup\Forms\ModulePage;
use Icinga\Module\Setup\Forms\WelcomePage;
use Icinga\Module\Setup\Forms\SummaryPage;
use Icinga\Module\Setup\Forms\DbResourcePage;
use Icinga\Module\Setup\Forms\PreferencesPage;
use Icinga\Module\Setup\Forms\AuthBackendPage;
use Icinga\Module\Setup\Forms\AdminAccountPage;
use Icinga\Module\Setup\Forms\LdapDiscoveryPage;
@ -91,7 +90,7 @@ class WebWizard extends Wizard implements SetupWizard
);
/**
* @see Wizard::init()
* Register all pages and module wizards for this wizard
*/
protected function init()
{
@ -99,15 +98,16 @@ class WebWizard extends Wizard implements SetupWizard
$this->addPage(new ModulePage());
$this->addPage(new RequirementsPage());
$this->addPage(new AuthenticationPage());
$this->addPage(new PreferencesPage());
$this->addPage(new DbResourcePage());
$this->addPage(new DbResourcePage(array('name' => 'setup_auth_db_resource')));
$this->addPage(new DatabaseCreationPage(array('name' => 'setup_auth_db_creation')));
$this->addPage(new LdapDiscoveryPage());
//$this->addPage(new LdapDiscoveryConfirmPage());
$this->addPage(new LdapResourcePage());
$this->addPage(new AuthBackendPage());
$this->addPage(new AdminAccountPage());
$this->addPage(new GeneralConfigPage());
$this->addPage(new DatabaseCreationPage());
$this->addPage(new DbResourcePage(array('name' => 'setup_config_db_resource')));
$this->addPage(new DatabaseCreationPage(array('name' => 'setup_config_db_creation')));
$this->addPage(new SummaryPage(array('name' => 'setup_summary')));
if (($modulePageData = $this->getPageData('setup_modules')) !== null) {
@ -119,25 +119,19 @@ class WebWizard extends Wizard implements SetupWizard
}
/**
* @see Wizard::setupPage()
* Setup the given page that is either going to be displayed or validated
*
* @param Form $page The page to setup
* @param Request $request The current request
*/
public function setupPage(Form $page, Request $request)
{
if ($page->getName() === 'setup_requirements') {
$page->setWizard($this);
} elseif ($page->getName() === 'setup_preferences_type') {
$authData = $this->getPageData('setup_authentication_type');
if ($authData['type'] === 'db') {
$page->create()->getElement('store')->setValue('db');
$page->addDescription(mt(
'setup',
'Note that choosing "Database" causes Icinga Web 2 to use the same database as for authentication.'
));
}
} elseif ($page->getName() === 'setup_authentication_backend') {
$authData = $this->getPageData('setup_authentication_type');
if ($authData['type'] === 'db') {
$page->setResourceConfig($this->getPageData('setup_db_resource'));
$page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
} elseif ($authData['type'] === 'ldap') {
$page->setResourceConfig($this->getPageData('setup_ldap_resource'));
@ -152,38 +146,46 @@ class WebWizard extends Wizard implements SetupWizard
$page->setBackendConfig($this->getPageData('setup_authentication_backend'));
$authData = $this->getPageData('setup_authentication_type');
if ($authData['type'] === 'db') {
$page->setResourceConfig($this->getPageData('setup_db_resource'));
$page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
} elseif ($authData['type'] === 'ldap') {
$page->setResourceConfig($this->getPageData('setup_ldap_resource'));
}
} elseif ($page->getName() === 'setup_database_creation') {
} elseif ($page->getName() === 'setup_auth_db_creation' || $page->getName() === 'setup_config_db_creation') {
$page->setDatabaseSetupPrivileges(
array_unique(array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges))
);
$page->setDatabaseUsagePrivileges($this->databaseUsagePrivileges);
$page->setResourceConfig($this->getPageData('setup_db_resource'));
$page->setResourceConfig(
$this->getPageData('setup_auth_db_resource') ?: $this->getPageData('setup_config_db_resource')
);
} elseif ($page->getName() === 'setup_summary') {
$page->setSubjectTitle('Icinga Web 2');
$page->setSummary($this->getSetup()->getSummary());
} elseif ($page->getName() === 'setup_db_resource') {
} elseif ($page->getName() === 'setup_config_db_resource') {
$ldapData = $this->getPageData('setup_ldap_resource');
if ($ldapData !== null && $request->getPost('name') === $ldapData['name']) {
$page->addError(
$page->error(
mt('setup', 'The given resource name must be unique and is already in use by the LDAP resource')
);
}
} elseif ($page->getName() === 'setup_ldap_resource') {
$dbData = $this->getPageData('setup_db_resource');
if ($dbData !== null && $request->getPost('name') === $dbData['name']) {
$page->addError(
mt('setup', 'The given resource name must be unique and is already in use by the database resource')
);
}
$suggestion = $this->getPageData('setup_ldap_discovery');
if (isset($suggestion['resource'])) {
$page->populate($suggestion['resource']);
}
} elseif ($page->getName() === 'setup_general_config') {
$authData = $this->getPageData('setup_authentication_type');
if ($authData['type'] === 'db') {
$page->create()->getElement('global_config_backend')->setValue('db');
$page->info(
mt(
'setup',
'Note that choosing "Database" as preference storage causes'
. ' Icinga Web 2 to use the same database as for authentication.'
),
false
);
}
} elseif ($page->getName() === 'setup_authentication_type' && $this->getDirection() === static::FORWARD) {
$authData = $this->getPageData($page->getName());
if ($authData !== null && $request->getPost('type') !== $authData['type']) {
@ -192,21 +194,37 @@ class WebWizard extends Wizard implements SetupWizard
$pageData = & $this->getPageData();
unset($pageData['setup_admin_account']);
unset($pageData['setup_authentication_backend']);
if ($authData['type'] === 'db') {
unset($pageData['setup_auth_db_resource']);
unset($pageData['setup_auth_db_creation']);
} elseif ($request->getPost('type') === 'db') {
unset($pageData['setup_config_db_resource']);
unset($pageData['setup_config_db_creation']);
}
}
}
}
/**
* @see Wizard::getNewPage()
* Return the new page to set as current page
*
* {@inheritdoc} Runs additional checks related to some registered pages.
*
* @param string $requestedPage The name of the requested page
* @param Form $originPage The origin page
*
* @return Form The new page
*
* @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
*/
protected function getNewPage($requestedPage, Form $originPage)
{
$skip = false;
$newPage = parent::getNewPage($requestedPage, $originPage);
if ($newPage->getName() === 'setup_db_resource') {
$prefData = $this->getPageData('setup_preferences_type');
if ($newPage->getName() === 'setup_auth_db_resource') {
$authData = $this->getPageData('setup_authentication_type');
$skip = $prefData['store'] !== 'db' && $authData['type'] !== 'db';
$skip = $authData['type'] !== 'db';
} elseif ($newPage->getname() === 'setup_ldap_discovery') {
$authData = $this->getPageData('setup_authentication_type');
$skip = $authData['type'] !== 'ldap';
@ -215,13 +233,22 @@ class WebWizard extends Wizard implements SetupWizard
} elseif ($newPage->getName() === 'setup_ldap_resource') {
$authData = $this->getPageData('setup_authentication_type');
$skip = $authData['type'] !== 'ldap';
} elseif ($newPage->getName() === 'setup_database_creation') {
if (($config = $this->getPageData('setup_db_resource')) !== null && ! $config['skip_validation']) {
} elseif ($newPage->getName() === 'setup_config_db_resource') {
$authData = $this->getPageData('setup_authentication_type');
$configData = $this->getPageData('setup_general_config');
$skip = $authData['type'] === 'db' || $configData['global_config_backend'] !== 'db';
} elseif (in_array($newPage->getName(), array('setup_auth_db_creation', 'setup_config_db_creation'))) {
if (
($newPage->getName() === 'setup_auth_db_creation' || $this->hasPageData('setup_config_db_resource'))
&& (($config = $this->getPageData('setup_auth_db_resource')) !== null
|| ($config = $this->getPageData('setup_config_db_resource')) !== null)
&& !$config['skip_validation']
) {
$db = new DbTool($config);
try {
$db->connectToDb(); // Are we able to login on the database?
if (array_search(key($this->databaseTables), $db->listTables()) === false) {
if (array_search(reset($this->databaseTables), $db->listTables()) === false) {
// In case the database schema does not yet exist the
// user needs the privileges to setup the database
$skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables);
@ -254,7 +281,9 @@ class WebWizard extends Wizard implements SetupWizard
}
/**
* @see Wizard::addButtons()
* Add buttons to the given page based on its position in the page-chain
*
* @param Form $page The page to add the buttons to
*/
protected function addButtons(Form $page)
{
@ -270,7 +299,7 @@ class WebWizard extends Wizard implements SetupWizard
}
/**
* @see Wizard::clearSession()
* Clear the session being used by this wizard and drop the setup token
*/
public function clearSession()
{
@ -283,29 +312,54 @@ class WebWizard extends Wizard implements SetupWizard
}
/**
* @see SetupWizard::getSetup()
* Return the setup for this wizard
*
* @return Setup
*/
public function getSetup()
{
$pageData = $this->getPageData();
$setup = new Setup();
if (isset($pageData['setup_db_resource'])
&& ! $pageData['setup_db_resource']['skip_validation']
&& (false === isset($pageData['setup_database_creation'])
|| ! $pageData['setup_database_creation']['skip_validation']
if (
isset($pageData['setup_auth_db_resource'])
&& !$pageData['setup_auth_db_resource']['skip_validation']
&& (! isset($pageData['setup_auth_db_creation'])
|| !$pageData['setup_auth_db_creation']['skip_validation']
)
) {
$setup->addStep(
new DatabaseStep(array(
'tables' => $this->databaseTables,
'privileges' => $this->databaseUsagePrivileges,
'resourceConfig' => $pageData['setup_db_resource'],
'adminName' => isset($pageData['setup_database_creation']['username'])
? $pageData['setup_database_creation']['username']
'resourceConfig' => $pageData['setup_auth_db_resource'],
'adminName' => isset($pageData['setup_auth_db_creation']['username'])
? $pageData['setup_auth_db_creation']['username']
: null,
'adminPassword' => isset($pageData['setup_database_creation']['password'])
? $pageData['setup_database_creation']['password']
'adminPassword' => isset($pageData['setup_auth_db_creation']['password'])
? $pageData['setup_auth_db_creation']['password']
: null,
'schemaPath' => Config::module('setup')
->get('schema', 'path', Icinga::app()->getBaseDir('etc' . DIRECTORY_SEPARATOR . 'schema'))
))
);
} elseif (
isset($pageData['setup_config_db_resource'])
&& !$pageData['setup_config_db_resource']['skip_validation']
&& (! isset($pageData['setup_config_db_creation'])
|| !$pageData['setup_config_db_creation']['skip_validation']
)
) {
$setup->addStep(
new DatabaseStep(array(
'tables' => $this->databaseTables,
'privileges' => $this->databaseUsagePrivileges,
'resourceConfig' => $pageData['setup_config_db_resource'],
'adminName' => isset($pageData['setup_config_db_creation']['username'])
? $pageData['setup_config_db_creation']['username']
: null,
'adminPassword' => isset($pageData['setup_config_db_creation']['password'])
? $pageData['setup_config_db_creation']['password']
: null,
'schemaPath' => Config::module('setup')
->get('schema', 'path', Icinga::app()->getBaseDir('etc' . DIRECTORY_SEPARATOR . 'schema'))
@ -315,22 +369,24 @@ class WebWizard extends Wizard implements SetupWizard
$setup->addStep(
new GeneralConfigStep(array(
'generalConfig' => $pageData['setup_general_config'],
'preferencesStore' => $pageData['setup_preferences_type']['store'],
'preferencesResource' => isset($pageData['setup_db_resource']['name'])
? $pageData['setup_db_resource']['name']
: null
'generalConfig' => $pageData['setup_general_config'],
'resourceName' => isset($pageData['setup_auth_db_resource']['name'])
? $pageData['setup_auth_db_resource']['name']
: (isset($pageData['setup_config_db_resource']['name'])
? $pageData['setup_config_db_resource']['name']
: null
)
))
);
$adminAccountType = $pageData['setup_admin_account']['user_type'];
$adminAccountData = array('username' => $pageData['setup_admin_account'][$adminAccountType]);
if ($adminAccountType === 'new_user' && ! $pageData['setup_db_resource']['skip_validation']
&& (false === isset($pageData['setup_database_creation'])
|| ! $pageData['setup_database_creation']['skip_validation']
if ($adminAccountType === 'new_user' && !$pageData['setup_auth_db_resource']['skip_validation']
&& (! isset($pageData['setup_auth_db_creation'])
|| !$pageData['setup_auth_db_creation']['skip_validation']
)
) {
$adminAccountData['resourceConfig'] = $pageData['setup_db_resource'];
$adminAccountData['resourceConfig'] = $pageData['setup_auth_db_resource'];
$adminAccountData['password'] = $pageData['setup_admin_account']['new_user_password'];
}
$authType = $pageData['setup_authentication_type']['type'];
@ -338,18 +394,25 @@ class WebWizard extends Wizard implements SetupWizard
new AuthenticationStep(array(
'adminAccountData' => $adminAccountData,
'backendConfig' => $pageData['setup_authentication_backend'],
'resourceName' => $authType === 'db' ? $pageData['setup_db_resource']['name'] : (
'resourceName' => $authType === 'db' ? $pageData['setup_auth_db_resource']['name'] : (
$authType === 'ldap' ? $pageData['setup_ldap_resource']['name'] : null
)
))
);
if (isset($pageData['setup_db_resource']) || isset($pageData['setup_ldap_resource'])) {
if (
isset($pageData['setup_auth_db_resource'])
|| isset($pageData['setup_config_db_resource'])
|| isset($pageData['setup_ldap_resource'])
) {
$setup->addStep(
new ResourceStep(array(
'dbResourceConfig' => isset($pageData['setup_db_resource'])
? array_diff_key($pageData['setup_db_resource'], array('skip_validation' => null))
: null,
'dbResourceConfig' => isset($pageData['setup_auth_db_resource'])
? array_diff_key($pageData['setup_auth_db_resource'], array('skip_validation' => null))
: (isset($pageData['setup_config_db_resource'])
? array_diff_key($pageData['setup_config_db_resource'], array('skip_validation' => null))
: null
),
'ldapResourceConfig' => isset($pageData['setup_ldap_resource'])
? array_diff_key($pageData['setup_ldap_resource'], array('skip_validation' => null))
: null
@ -369,7 +432,9 @@ class WebWizard extends Wizard implements SetupWizard
}
/**
* @see SetupWizard::getRequirements()
* Return the requirements of this wizard
*
* @return RequirementSet
*/
public function getRequirements($skipModules = false)
{

View File

@ -268,4 +268,4 @@ form ul.hints {
font-size: 0.8em;
padding-bottom: 0.5em;
}
}
}

View File

@ -389,6 +389,14 @@ html {
width: 1px;
}
.clearfix:after {
content: ".";
visibility: hidden;
display: block;
height: 0;
clear: both;
}
.multi-commands {
padding-top: 0em;
font-size: 0.9em;

View File

@ -143,6 +143,7 @@
&:hover, &:focus, &:active {
background-color: #666;
border-color: #666;
cursor: pointer;
&[disabled="1"] {
background-color: #aaa;
@ -182,43 +183,51 @@ form#setup_requirements {
}
}
#setup > table.requirements {
font-size: 0.9em;
}
#setup ul.requirements {
margin: 0;
padding: 0;
list-style-type: none;
#setup table.requirements {
margin: -1em;
border-spacing: 1em;
border-collapse: separate;
li {
margin-bottom: 1em;
td {
padding: 0;
h2 {
margin: 0 1em 0 0;
font-variant: normal;
border-bottom: 0;
}
table {
font-size: 102%; // Just a hack for webkit, remove this in case you can't see any difference or make it work without it
}
ul {
& > ul {
margin: 0;
padding-left: 1em;
list-style-type: square;
padding: 0;
list-style-type: none;
}
&.title {
div {
float: left;
padding-top: 0.4em;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
div.title {
width: 25%;
h3 {
padding: 0;
margin: 0 1em 0 0;
border-bottom: 0;
}
}
&.desc {
div.description {
width: 50%;
border-left: 0.4em solid transparent;
border-right: 0.4em solid transparent;
ul {
margin: 0;
padding-left: 1em;
list-style-type: square;
}
}
&.state {
div.state {
width: 25%;
color: white;
padding: 0.4em;
@ -300,50 +309,35 @@ form#setup_requirements {
}
}
.conspicuous-state-notification {
width: 66%;
margin: 0 auto;
padding: 0.5em;
color: white;
font-weight: bold;
}
#setup-finish {
div.report {
padding: 1em;
border: 1px solid lightgrey;
border-radius: 0em;
h2 {
padding: 0.5em;
border-bottom: 0;
font-variant: normal;
font-weight: bold;
color: white;
div.line-separator {
width: 50%;
height: 1px;
margin: 0 auto;
background-color: white;
&.success {
background-color: @colorOk;
}
p {
margin: 1em;
color: #444;
text-align: center;
&.error {
color: red;
}
&.failure {
.conspicuous-state-notification;
background-color: @colorCritical;
}
&.success {
.conspicuous-state-notification;
background-color: @colorOk;
}
&.failure {
background-color: @colorCritical;
}
}
textarea.report {
width: 66%;
height: 25em;
}
div.buttons {
margin-top: 0.5em;
text-align: center;
a {
padding: 0.5em;
}
}
}

View File

@ -0,0 +1,396 @@
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
/**
* Icinga.Behavior.ActionTable
*
* A multi selection that distincts between the table rows using the row action URL filter
*/
(function(Icinga, $) {
"use strict";
/**
* Remove one leading and trailing bracket and all text outside those brackets
*
* @param str {String}
* @returns {string}
*/
var stripBrackets = function (str) {
return str.replace(/^[^\(]*\(/, '').replace(/\)[^\)]*$/, '');
};
/**
* Parse the filter query contained in the given url filter string
*
* @param filterString {String}
*
* @returns {Array} An object containing each row filter
*/
var parseSelectionQuery = function(filterString) {
var selections = [];
$.each(stripBrackets(filterString).split('|'), function(i, row) {
var tuple = {};
$.each(stripBrackets(row).split('&'), function(i, keyValue) {
var s = keyValue.split('=');
tuple[s[0]] = decodeURIComponent(s[1]);
});
selections.push(tuple);
});
return selections;
};
/**
* Handle the selection of an action table
*
* @param table {HTMLElement} The table
* @param {Icinga}
*
* @constructor
*/
var Selection = function(table, icinga) {
this.$el = $(table);
this.icinga = icinga;
if (this.hasMultiselection()) {
if (! this.getMultiselectionKeys().length) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
}
if (! this.getMultiselectionUrl()) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
}
}
};
Selection.prototype = {
/**
* Return all rows as jQuery selector
*
* @returns {jQuery}
*/
rows: function() {
return this.$el.find('tr');
},
/**
* Return all row action links as jQuery selector
*
* @returns {jQuery}
*/
rowActions: function() {
return this.$el.find('tr a.rowaction');
},
/**
* Return all selected rows as jQuery selector
*
* @returns {jQuery}
*/
selections: function() {
return this.$el.find('tr.active');
},
/**
* If this selection allows selecting multiple rows
*
* @returns {Boolean}
*/
hasMultiselection: function() {
return this.$el.hasClass('multiselect');
},
/**
* Return all filter keys that are significant when applying the selection
*
* @returns {Array}
*/
getMultiselectionKeys: function() {
var data = this.$el.data('icinga-multiselect-data');
return (data && data.split(',')) || [];
},
/**
* Return the target URL that is used when multi selecting rows
*
* This URL may differ from the url that is used when applying single rows
*
* @returns {String}
*/
getMultiselectionUrl: function() {
return this.$el.data('icinga-multiselect-url');
},
/**
* Read all filter data from the given row
*
* @param row {jQuery} The row element
*
* @returns {Object} An object containing all filter data in this row as key-value pairs
*/
getRowData: function(row) {
var params = this.icinga.utils.parseUrl(row.attr('href')).params;
var tuple = {};
var keys = this.getMultiselectionKeys();
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (params[key]) {
tuple[key] = params[key];
}
}
return tuple;
},
/**
* Deselect all selected rows
*/
clear: function() {
this.selections().removeClass('active');
},
/**
* Add all rows that match the given filter to the selection
*
* @param filter {jQuery|Object} Either an object containing filter variables or the actual row to select
*/
select: function(filter) {
if (filter instanceof jQuery) {
filter.addClass('active');
return;
}
var self = this;
var url = this.getMultiselectionUrl();
this.rowActions()
.filter(
function (i, el) {
var params = self.getRowData($(el));
if (self.icinga.utils.objectKeys(params).length !== self.icinga.utils.objectKeys(filter).length) {
return false;
}
var equal = true;
$.each(params, function(key, value) {
if (filter[key] !== value) {
equal = false;
}
});
return equal;
}
)
.closest('tr')
.addClass('active');
},
/**
* Toggle the selection of the row between on and off
*
* @param row {jQuery} The row to toggle
*/
toggle: function(row) {
row.toggleClass('active');
},
/**
* Add a new selection range to the closest table, using the selected row as
* range target.
*
* @param row {jQuery} The target of the selected range.
*
* @returns {boolean} If the selection was changed.
*/
range: function(row) {
var from, to;
var selected = row.first().get(0);
this.rows().each(function(i, el) {
if ($(el).hasClass('active') || el === selected) {
if (!from) {
from = el;
}
to = el;
}
});
var inRange = false;
this.rows().each(function(i, el) {
if (el === from) {
inRange = true;
}
if (inRange) {
$(el).addClass('active');
}
if (el === to) {
inRange = false;
}
});
return false;
},
/**
* Select rows that target the given url
*
* @param url {String} The target url
*/
selectUrl: function(url) {
this.rows().filter('[href="' + url + '"]').addClass('active');
},
/**
* Convert all currently selected rows into an url query string
*
* @returns {String} The filter string
*/
toQuery: function() {
var self = this;
var selections = this.selections();
var queries = [];
if (selections.length === 1) {
return $(selections[0]).attr('href');
} else if (selections.length > 1 && self.hasMultiselection()) {
selections.each(function (i, el) {
var parts = [];
$.each(self.getRowData($(el)), function(key, value) {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
queries.push('(' + parts.join('&') + ')');
});
return self.getMultiselectionUrl() + '?(' + queries.join('|') + ')';
} else {
return '';
}
},
/**
* Refresh the displayed active columns using the current page location
*/
refresh: function() {
this.clear();
var hash = this.icinga.utils.parseUrl(window.location.href).hash;
if (this.hasMultiselection()) {
var query = parseSelectionQuery(hash);
if (query.length > 1 && this.getMultiselectionUrl() === this.icinga.utils.parseUrl(hash.substr(1)).path) {
// select all rows with matching filters
var self = this;
$.each(query, function(i, selection) {
self.select(selection);
});
}
if (query.length > 1) {
return;
}
}
this.selectUrl(hash.substr(1));
}
};
Icinga.Behaviors = Icinga.Behaviors || {};
var ActionTable = function (icinga) {
Icinga.EventListener.call(this, icinga);
/**
* The hash that is currently being loaded
*
* @var String
*/
this.loadingHash = null;
/**
* If currently loading
*
* @var Boolean
*/
this.loading = false;
this.on('rendered', this.onRendered, this);
this.on('click', 'table.action tr[href]', this.onRowClicked, this);
};
ActionTable.prototype = new Icinga.EventListener();
/**
* Return all active tables in this table, or in the context as jQuery selector
*
* @param context {HTMLElement}
* @returns {jQuery}
*/
ActionTable.prototype.tables = function(context) {
if (context) {
return $(context).find('table.action');
}
return $('table.action');
};
/**
* Handle clicks on table rows and update selection and history
*/
ActionTable.prototype.onRowClicked = function (event) {
var self = event.data.self;
var $tr = $(event.target).closest('tr');
var table = new Selection($tr.closest('table.action')[0], self.icinga);
// allow form actions in table rows to pass through
if ($(event.target).closest('form').length) {
return;
}
event.stopPropagation();
event.preventDefault();
// update selection
if (table.hasMultiselection()) {
if (event.ctrlKey || event.metaKey) {
// add to selection
table.toggle($tr);
} else if (event.shiftKey) {
// range selection
table.range($tr);
} else {
// single selection
table.clear();
table.select($tr);
}
} else {
table.clear();
table.select($tr);
}
// update history
var url = self.icinga.utils.parseUrl(window.location.href.split('#')[0]);
var count = table.selections().length;
var state = url.path + url.query;
if (count > 0) {
var query = table.toQuery();
self.icinga.loader.loadUrl(query, self.icinga.events.getLinkTargetFor($tr));
state += '#!' + query;
} else {
if (self.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') {
self.icinga.ui.layout1col();
}
}
self.icinga.history.pushUrl(state);
// re draw all table selections
self.tables().each(function () {
new Selection(this, self.icinga).refresh();
});
// update selection info
$('.selection-info-count').text(table.selections().size());
return false;
};
/**
* Ensure that
*/
ActionTable.prototype.onRendered = function(evt) {
var container = evt.target;
var self = evt.data.self;
// draw all selections
self.tables().each(function(i, el) {
new Selection(el, self.icinga).refresh();
});
// update displayed selection count
var table = new Selection(self.tables(container).first());
$(container).find('.selection-info-count').text(table.selections().size());
};
Icinga.Behaviors.ActionTable = ActionTable;
}) (Icinga, jQuery);

View File

@ -117,9 +117,6 @@
$(document).on('click', 'a', { self: this }, this.linkClicked);
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
// Select a table row
$(document).on('click', 'table.multiselect tr[href]', { self: this }, this.rowSelected);
// We catch all form submit events
$(document).on('submit', 'form', { self: this }, this.submitForm);
@ -303,74 +300,6 @@
return false;
},
/**
* Handle table selection.
*/
rowSelected: function(event) {
var self = event.data.self;
var icinga = self.icinga;
var $tr = $(this);
var $table = $tr.closest('table.multiselect');
var data = self.icinga.ui.getSelectionKeys($table);
var url = $table.data('icinga-multiselect-url');
if ($(event.target).closest('form').length) {
// allow form actions in table rows to pass through
return;
}
event.stopPropagation();
event.preventDefault();
if (!data) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
return;
}
if (!url) {
icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
return;
}
// update selection
if (event.ctrlKey || event.metaKey) {
icinga.ui.toogleTableRowSelection($tr);
// multi selection
} else if (event.shiftKey) {
// range selection
icinga.ui.addTableRowRangeSelection($tr);
} else {
// single selection
icinga.ui.setTableRowSelection($tr);
}
// focus only the current table.
icinga.ui.focusTable($table[0]);
var $target = self.getLinkTargetFor($tr);
var $trs = $table.find('tr[href].active');
if ($trs.length > 1) {
var selectionData = icinga.ui.getSelectionSetData($trs, data);
var query = icinga.ui.selectionDataToQuery(selectionData);
icinga.loader.loadUrl(url + '?' + query, $target);
icinga.ui.storeSelectionData(selectionData);
icinga.ui.provideSelectionCount();
} else if ($trs.length === 1) {
// display a single row
$tr = $trs.first();
icinga.loader.loadUrl($tr.attr('href'), $target);
icinga.ui.storeSelectionData($tr.attr('href'));
icinga.ui.provideSelectionCount();
} else {
// display nothing
if ($target.attr('id') === 'col2') {
icinga.ui.layout1col();
}
icinga.ui.storeSelectionData(null);
icinga.ui.provideSelectionCount();
}
return false;
},
/**
* Handle anchor, i.e. focus the element which is referenced by the anchor
*
@ -406,16 +335,16 @@
// Special checks for link clicks in multiselect rows
if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) {
// Forward clicks to ANY link with special key pressed to rowSelected
// ignoray clicks to ANY link with special key pressed
if (event.ctrlKey || event.metaKey || event.shiftKey)
{
return self.rowSelected.call($a.closest('tr[href]'), event);
return true;
}
// Forward inner links matching the row URL to rowSelected
// ignore inner links matching the row URL
if ($a.attr('href') === $a.closest('tr[href]').attr('href'))
{
return self.rowSelected.call($a.closest('tr[href]'), event);
return true;
}
}
@ -477,8 +406,6 @@
icinga.ui.layout1col();
}
$('table tr[href].active').removeClass('active');
icinga.ui.storeSelectionData(null);
icinga.ui.loadSelectionData();
icinga.history.pushCurrentState();
}
}
@ -574,8 +501,6 @@
$(window).off('beforeunload', this.onUnload);
$(document).off('scroll', '.container', this.onContainerScroll);
$(document).off('click', 'a', this.linkClicked);
$(document).off('click', 'table.action tr[href]', this.rowSelected);
$(document).off('click', 'table.action tr a', this.rowSelected);
$(document).off('submit', 'form', this.submitForm);
$(document).off('change', 'form select.autosubmit', this.submitForm);
$(document).off('change', 'form input.autosubmit', this.submitForm);

View File

@ -116,7 +116,6 @@
req.complete(this.onComplete);
req.autorefresh = autorefresh;
req.action = action;
req.failure = false;
req.addToHistory = true;
if (id) {
@ -453,6 +452,8 @@
var $el = $(el);
if ($el.hasClass('dashboard')) {
return;
} else {
}
var url = $el.data('icingaUrl');
targets[i].data('icingaUrl', url);
@ -504,42 +505,6 @@
this.icinga.ui.fixDebugVisibility().triggerWindowResize();
}
self.cacheLoadedIcons(req.$target);
if (active) {
var focusedUrl = this.icinga.ui.getFocusedContainerDataUrl();
var oldSelectionData = this.icinga.ui.loadSelectionData();
if (typeof oldSelectionData === 'string') {
$('[href="' + oldSelectionData + '"]', req.$target).addClass('active');
} else if (oldSelectionData !== null) {
var $container;
if (!focusedUrl) {
$container = $('document').first();
} else {
$container = $('.container[data-icinga-url="' + focusedUrl + '"]');
}
var $table = $container.find('table.action').first();
var keys = self.icinga.ui.getSelectionKeys($table);
// build map of selected queries
var oldSelectionQueries = {};
$.each(oldSelectionData, function(i, query){
oldSelectionQueries[self.icinga.ui.selectionDataToQueryComp(query)] = true;
});
// set all new selections to active
$table.find('tr[href]').filter(function(){
var $tr = $(this);
var rowData = self.icinga.ui.getSelectionData($tr, keys, self.icinga);
var newSelectionQuery = self.icinga.ui.selectionDataToQueryComp(rowData);
if (oldSelectionQueries[newSelectionQuery]) {
return true;
}
return false;
}).addClass('active');
}
}
},
/**
@ -587,7 +552,7 @@
// Update history when necessary. Don't do so for requests triggered
// by history or autorefresh events
if (! req.autorefresh && req.addToHistory) {
if (req.$target.hasClass('container') && ! req.failure) {
if (req.$target.hasClass('container')) {
// We only want to care about top-level containers
if (req.$target.parent().closest('.container').length === 0) {
this.icinga.history.pushCurrentState();
@ -595,7 +560,7 @@
} else {
// Request wasn't for a container, so it's usually the body
// or the full layout. Push request URL to history:
this.icinga.history.pushCurrentState();
this.icinga.history.pushUrl(req.url);
}
}
@ -623,10 +588,6 @@
onFailure: function (req, textStatus, errorThrown) {
var url = req.url;
req.failure = true;
req.$target.data('icingaUrl', req.url);
/*
* Test if a manual actions comes in and autorefresh is active: Stop refreshing
*/

View File

@ -9,13 +9,6 @@
'use strict';
// Stores the icinga-data-url of the last focused table.
var focusedTableDataUrl = null;
// The stored selection data, useful for preserving selections over
// multiple reload-cycles.
var selectionData = null;
Icinga.UI = function (icinga) {
this.icinga = icinga;
@ -38,8 +31,7 @@
this.fadeNotificationsAway();
},
fadeNotificationsAway: function()
{
fadeNotificationsAway: function() {
var icinga = this.icinga;
$('#notifications li')
.not('.fading-out')
@ -298,226 +290,6 @@
return $('#main > .container').length;
},
/**
* Add the given table-row to the selection of the closest
* table and deselect all other rows of the closest table.
*
* @param $tr {jQuery} The selected table row.
* @returns {boolean} If the selection was changed.
*/
setTableRowSelection: function ($tr) {
var $table = $tr.closest('table.multiselect');
$table.find('tr[href].active').removeClass('active');
$tr.addClass('active');
return true;
},
/**
* Toggle the given table row to "on" when not selected, or to "off" when
* currently selected.
*
* @param $tr {jQuery} The table row.
* @returns {boolean} If the selection was changed.
*/
toogleTableRowSelection: function ($tr) {
// multi selection
if ($tr.hasClass('active')) {
$tr.removeClass('active');
} else {
$tr.addClass('active');
}
return true;
},
/**
* Add a new selection range to the closest table, using the selected row as
* range target.
*
* @param $tr {jQuery} The target of the selected range.
* @returns {boolean} If the selection was changed.
*/
addTableRowRangeSelection: function ($tr) {
var $table = $tr.closest('table.multiselect');
var $rows = $table.find('tr[href]'),
from, to;
var selected = $tr.first().get(0);
$rows.each(function(i, el) {
if ($(el).hasClass('active') || el === selected) {
if (!from) {
from = el;
}
to = el;
}
});
var inRange = false;
$rows.each(function(i, el){
if (el === from) {
inRange = true;
}
if (inRange) {
$(el).addClass('active');
}
if (el === to) {
inRange = false;
}
});
return false;
},
/**
* Read the data from a whole set of selections.
*
* @param $selections {jQuery} All selected rows in a jQuery-selector.
* @param keys {Array} An array containing all valid keys.
* @returns {Array} An array containing an object with the data for each selection.
*/
getSelectionSetData: function($selections, keys) {
var selections = [];
var icinga = this.icinga;
// read all current selections
$selections.each(function(ind, selected) {
selections.push(icinga.ui.getSelectionData($(selected), keys, icinga));
});
return selections;
},
getSelectionKeys: function($selection)
{
var d = $selection.data('icinga-multiselect-data') && $selection.data('icinga-multiselect-data').split(',');
return d || [];
},
/**
* Read the data from the given selected object.
*
* @param $selection {jQuery} The selected object.
* @param keys {Array} An array containing all valid keys.
* @param icinga {Icinga} The main icinga object.
* @returns {Object} An object containing all key-value pairs associated with this selection.
*/
getSelectionData: function($selection, keys, icinga)
{
var url = $selection.attr('href');
var params = this.icinga.utils.parseUrl(url).params;
var tuple = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (params[key]) {
tuple[key] = params[key];
}
}
return tuple;
},
/**
* Convert a set of selection data to a single query.
*
* @param selectionData {Array} The selection data generated from getSelectionData
* @returns {String} The formatted and uri-encoded query-string.
*/
selectionDataToQuery: function (selectionData) {
var queries = [];
// create new url
if (selectionData.length < 2) {
this.icinga.logger.error('Something went wrong, we should never multiselect just one row');
} else {
$.each(selectionData, function(i, el){
var parts = []
$.each(el, function(key, value) {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
queries.push('(' + parts.join('&') + ')');
});
}
return '(' + queries.join('|') + ')';
},
/**
* Create a single query-argument (not compatible to selectionDataToQuery)
*
* @param data
* @returns {string}
*/
selectionDataToQueryComp: function(data) {
var queries = [];
$.each(data, function(key, value){
queries.push(key + '=' + encodeURIComponent(value));
});
return queries.join('&');
},
/**
* Store a set of selection-data to preserve it accross page-reloads
*
* @param data {Array|String|Null} The selection-data be an Array of Objects,
* containing the selection data (when multiple rows where selected), a
* String containing a single url (when only a single row was selected) or
* Null when nothing was selected.
*/
storeSelectionData: function(data) {
selectionData = data;
},
/**
* Load the last stored set of selection-data
*
* @returns {Array|String|Null} May be an Array of Objects, containing the selection data
* (when multiple rows where selected), a String containing a single url
* (when only a single row was selected) or Null when nothing was selected.
*/
loadSelectionData: function() {
this.provideSelectionCount();
return selectionData;
},
/**
* Set the selections row count hint info
*/
provideSelectionCount: function() {
var $count = $('.selection-info-count');
if (typeof selectionData === 'undefined' || selectionData === null) {
$count.text(0);
return;
}
if (typeof selectionData === 'string') {
$count.text(1);
} else if (selectionData.length > 1) {
$count.text(selectionData.length);
} else {
$count.text(0);
}
},
/**
* Focus the given table by deselecting all selections on all other tables.
*
* Focusing a table is important for environments with multiple tables like
* the dashboard. It should only be possible to select rows at one table at a time,
* when a user selects a row on a table all rows that are not child of the given table
* will be removed from the selection.
*
* @param table {htmlElement} The table to focus.
*/
focusTable: function (table) {
$('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active');
var n = $(table).closest('div.container').attr('data-icinga-url');
focusedTableDataUrl = n;
},
/**
* Return the URL of the last focused table container.
*
* @returns {String} The data-icinga-url of the last focused table, which should be unique in each site.
*/
getFocusedContainerDataUrl: function() {
return focusedTableDataUrl;
},
/**
* Assign a unique ID to each .container without such
*

View File

@ -293,6 +293,14 @@
return $element[0];
},
objectKeys: Object.keys || function (obj) {
var keys = [];
$.each(obj, function (key) {
keys.push(key);
});
return keys;
},
/**
* Cleanup
*/

View File

@ -26,7 +26,7 @@ class DbResourceFormTest extends BaseTestCase
public function testValidDbResourceIsValid()
{
$this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('getConnection')->atMost()->twice()->andReturn(Mockery::self())->getMock()
Mockery::mock()->shouldReceive('inspect')->andReturn(self::createInspector(false))->getMock()
);
$this->assertTrue(
@ -42,7 +42,7 @@ class DbResourceFormTest extends BaseTestCase
public function testInvalidDbResourceIsNotValid()
{
$this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('getConnection')->once()->andThrow('\Exception')->getMock()
Mockery::mock()->shouldReceive('inspect')->andReturn(self::createInspector(true))->getMock()
);
$this->assertFalse(
@ -58,4 +58,21 @@ class DbResourceFormTest extends BaseTestCase
->with(Mockery::type('Icinga\Data\ConfigObject'))
->andReturn($resourceMock);
}
public static function createInspector($error = false, $log = array('log'))
{
if (! $error) {
$calls = array(
'hasError' => false,
'toArray' => $log
);
} else {
$calls = array(
'hasError' => true,
'getError' => 'Error',
'toArray' => $log
);
}
return Mockery::mock('Icinga\Data\Inspection', $calls);
}
}

View File

@ -26,14 +26,16 @@ class LdapResourceFormTest extends BaseTestCase
public function testValidLdapResourceIsValid()
{
$this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('connect')->once()->shouldReceive('bind')->once()->getMock()
Mockery::mock()->shouldReceive('inspect')->andReturn(self::createInspector(false))->getMock()
);
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$this->assertTrue(
@ -49,14 +51,16 @@ class LdapResourceFormTest extends BaseTestCase
public function testInvalidLdapResourceIsNotValid()
{
$this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('connect')->once()->shouldReceive('bind')->andThrow('\Exception')->getMock()
Mockery::mock()->shouldReceive('inspect')->andReturn(self::createInspector(true))->getMock()
);
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$this->assertFalse(
@ -72,4 +76,21 @@ class LdapResourceFormTest extends BaseTestCase
->with(Mockery::type('Icinga\Data\ConfigObject'))
->andReturn($resourceMock);
}
public static function createInspector($error = false, $log = array('log'))
{
if (! $error) {
$calls = array(
'hasError' => false,
'toArray' => $log
);
} else {
$calls = array(
'hasError' => true,
'getError' => 'Error',
'toArray' => $log
);
}
return Mockery::mock('Icinga\Data\Inspection', $calls);
}
}

View File

@ -28,14 +28,16 @@ class DbBackendFormTest extends BaseTestCase
{
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\User\DbUserBackend')
->shouldReceive('select->where->count')
->andReturn(2);
->shouldReceive('inspect')
->andReturn(self::createInspector(false));
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\DbBackendForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$form->setResources(array('test_db_backend'));
$form->populate(array('resource' => 'test_db_backend'));
@ -54,14 +56,16 @@ class DbBackendFormTest extends BaseTestCase
{
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\User\DbUserBackend')
->shouldReceive('count')
->andReturn(0);
->shouldReceive('inspect')
->andReturn(self::createInspector(true));
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\DbBackendForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$form->setResources(array('test_db_backend'));
$form->populate(array('resource' => 'test_db_backend'));
@ -80,4 +84,21 @@ class DbBackendFormTest extends BaseTestCase
->shouldReceive('getResourceConfig')
->andReturn(new ConfigObject());
}
public static function createInspector($error = false, $log = array('log'))
{
if (! $error) {
$calls = array(
'hasError' => false,
'toArray' => $log
);
} else {
$calls = array(
'hasError' => true,
'getError' => 'Error',
'toArray' => $log
);
}
return Mockery::mock('Icinga\Data\Inspection', $calls);
}
}

View File

@ -12,6 +12,7 @@ use Icinga\Data\ConfigObject;
use Icinga\Test\BaseTestCase;
use Icinga\Forms\Config\UserBackend\LdapBackendForm;
use Icinga\Exception\AuthenticationException;
use Tests\Icinga\Forms\Config\Resource\LdapResourceFormTest;
class LdapBackendFormTest extends BaseTestCase
{
@ -27,15 +28,18 @@ class LdapBackendFormTest extends BaseTestCase
*/
public function testValidBackendIsValid()
{
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andReturnNull();
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
->shouldReceive('inspect')
->andReturn(self::createInspector(false))->getMock();
$this->setUpUserBackendMock($ldapUserBackendMock);
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$form->setResources(array('test_ldap_backend'));
$form->populate(array('resource' => 'test_ldap_backend'));
@ -52,7 +56,9 @@ class LdapBackendFormTest extends BaseTestCase
*/
public function testInvalidBackendIsNotValid()
{
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
->shouldReceive('inspect')
->andReturn(self::createInspector(true))->getMock();
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException);
$this->setUpUserBackendMock($ldapUserBackendMock);
@ -60,7 +66,9 @@ class LdapBackendFormTest extends BaseTestCase
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null));
$form->shouldReceive('getView->escape')
->with(Mockery::type('string'))
->andReturnUsing(function ($s) { return $s; });
->andReturnUsing(function ($s) {
return $s;
});
$form->setTokenDisabled();
$form->setResources(array('test_ldap_backend'));
$form->populate(array('resource' => 'test_ldap_backend'));
@ -77,4 +85,21 @@ class LdapBackendFormTest extends BaseTestCase
->shouldReceive('create')
->andReturn($ldapUserBackendMock);
}
public static function createInspector($error = false, $log = array('log'))
{
if (! $error) {
$calls = array(
'hasError' => false,
'toArray' => $log
);
} else {
$calls = array(
'hasError' => true,
'getError' => 'Error',
'toArray' => $log
);
}
return Mockery::mock('Icinga\Data\Inspection', $calls);
}
}