mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-20 04:14:24 +02:00
Merge remote-tracking branch 'origin/master' into feature/refine-ui-for-rc1-9361
This commit is contained in:
commit
0170d9941a
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -6,3 +6,6 @@ Vagrantfile export-ignore
|
||||
|
||||
# Normalize puppet manifests' line endings to LF on checkin and prevent conversion to CRLF when the files are checked out
|
||||
.puppet* eol=lf
|
||||
|
||||
# Include version information on `git archive'
|
||||
/application/VERSION export-subst
|
||||
|
1
AUTHORS
1
AUTHORS
@ -18,6 +18,7 @@ Marius Hein <marius.hein@netways.de>
|
||||
Markus Frosch <markus@lazyfrosch.de>
|
||||
Matthias Jentsch <matthias.jentsch@netways.de>
|
||||
Michael Friedrich <michael.friedrich@netways.de>
|
||||
Paul Richards <paul@minimoo.org>
|
||||
Rene Moser <rene.moser@swisstxt.ch>
|
||||
Susanne Vestner-Ludwig <susanne.vestner-ludwig@inserteffect.com>
|
||||
Sylph Lin <sylph.lin@gmail.com>
|
||||
|
1
application/VERSION
Normal file
1
application/VERSION
Normal file
@ -0,0 +1 @@
|
||||
$Format:%H%d %ci$
|
15
application/controllers/AboutController.php
Normal file
15
application/controllers/AboutController.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
# namespace Icinga\Application\Controllers;
|
||||
|
||||
use Icinga\Web\Controller\ActionController;
|
||||
use Icinga\Application\Version;
|
||||
|
||||
class AboutController extends ActionController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->version = Version::get();
|
||||
}
|
||||
}
|
@ -28,6 +28,10 @@ class GroupController extends AuthBackendController
|
||||
function ($b) { return $b->getName(); },
|
||||
$this->loadUserGroupBackends('Icinga\Data\Selectable')
|
||||
);
|
||||
if (empty($backendNames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->view->backendSelection = new Form();
|
||||
$this->view->backendSelection->setAttrib('class', 'backend-selection');
|
||||
$this->view->backendSelection->setUidDisabled();
|
||||
@ -100,6 +104,7 @@ class GroupController extends AuthBackendController
|
||||
|
||||
$filterEditor = Widget::create('filterEditor')
|
||||
->setQuery($members)
|
||||
->setSearchColumns(array('user'))
|
||||
->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'group')
|
||||
->ignoreParams('page')
|
||||
->handleRequest($this->getRequest());
|
||||
|
@ -28,6 +28,10 @@ class UserController extends AuthBackendController
|
||||
function ($b) { return $b->getName(); },
|
||||
$this->loadUserBackends('Icinga\Data\Selectable')
|
||||
);
|
||||
if (empty($backendNames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->view->backendSelection = new Form();
|
||||
$this->view->backendSelection->setAttrib('class', 'backend-selection');
|
||||
$this->view->backendSelection->setUidDisabled();
|
||||
@ -99,6 +103,7 @@ class UserController extends AuthBackendController
|
||||
|
||||
$filterEditor = Widget::create('filterEditor')
|
||||
->setQuery($memberships)
|
||||
->setSearchColumns(array('group_name'))
|
||||
->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'user')
|
||||
->ignoreParams('page')
|
||||
->handleRequest($this->getRequest());
|
||||
|
@ -52,7 +52,7 @@ class UsergroupbackendController extends Controller
|
||||
$form->setIniConfig(Config::app('groups'));
|
||||
$form->setOnSuccess(function (UserGroupBackendForm $form) {
|
||||
try {
|
||||
$form->add($form->getValues());
|
||||
$form->add(array_filter($form->getValues()));
|
||||
} catch (Exception $e) {
|
||||
$form->error($e->getMessage());
|
||||
return false;
|
||||
@ -85,7 +85,12 @@ class UsergroupbackendController extends Controller
|
||||
$form->setIniConfig(Config::app('groups'));
|
||||
$form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) {
|
||||
try {
|
||||
$form->edit($backendName, $form->getValues());
|
||||
$form->edit($backendName, array_map(
|
||||
function ($v) {
|
||||
return $v !== '' ? $v : null;
|
||||
},
|
||||
$form->getValues()
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
$form->error($e->getMessage());
|
||||
return false;
|
||||
|
147
application/forms/Config/Resource/SshResourceForm.php
Normal file
147
application/forms/Config/Resource/SshResourceForm.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Config\Resource;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Forms\Config\ResourceConfigForm;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Util\File;
|
||||
use Zend_Validate_Callback;
|
||||
|
||||
/**
|
||||
* Form class for adding/modifying ssh identity resources
|
||||
*/
|
||||
class SshResourceForm extends Form
|
||||
{
|
||||
/**
|
||||
* Initialize this form
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->setName('form_config_resource_ssh');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Form::createElements()
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$this->addElement(
|
||||
'text',
|
||||
'name',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('Resource Name'),
|
||||
'description' => $this->translate('The unique name of this resource')
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('User'),
|
||||
'description' => $this->translate(
|
||||
'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
|
||||
. ' possible for this user'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if ($this->getRequest()->getActionName() != 'editresource') {
|
||||
|
||||
$callbackValidator = new Zend_Validate_Callback(function ($value) {
|
||||
if (openssl_pkey_get_private($value) === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
$callbackValidator->setMessage(
|
||||
$this->translate('The given SSH key is invalid'),
|
||||
Zend_Validate_Callback::INVALID_VALUE
|
||||
);
|
||||
|
||||
$this->addElement(
|
||||
'textarea',
|
||||
'private_key',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('Private Key'),
|
||||
'description' => $this->translate('The private key which will be used for the SSH connections'),
|
||||
'class' => 'resource ssh-identity',
|
||||
'validators' => array($callbackValidator)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$resourceName = $formData['name'];
|
||||
$this->addElement(
|
||||
'note',
|
||||
'private_key_note',
|
||||
array(
|
||||
'escape' => false,
|
||||
'label' => $this->translate('Private Key'),
|
||||
'value' => sprintf(
|
||||
'<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
|
||||
$this->getView()->url('config/removeresource', array('resource' => $resourceName)),
|
||||
sprintf($this->translate(
|
||||
'Remove the %s resource'
|
||||
), $resourceName),
|
||||
$this->translate('To modify the private key you must recreate this resource.')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the assigned key to the resource
|
||||
*
|
||||
* @param ConfigObject $config
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function beforeRemove(ConfigObject $config)
|
||||
{
|
||||
$file = $config->private_key;
|
||||
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the assigned key to the resource
|
||||
*
|
||||
* @param ResourceConfigForm $form
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function beforeAdd(ResourceConfigForm $form)
|
||||
{
|
||||
$configDir = Icinga::app()->getConfigDir();
|
||||
$user = $form->getElement('user')->getValue();
|
||||
|
||||
$filePath = $configDir . '/ssh/' . $user;
|
||||
|
||||
if (! file_exists($filePath)) {
|
||||
$file = File::create($filePath, 0600);
|
||||
} else {
|
||||
$form->error(
|
||||
sprintf($form->translate('The private key for the user "%s" is already exists.'), $user)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$file->fwrite($form->getElement('private_key')->getValue());
|
||||
|
||||
$form->getElement('private_key')->setValue($configDir . '/ssh/' . $user);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use Icinga\Forms\Config\Resource\DbResourceForm;
|
||||
use Icinga\Forms\Config\Resource\FileResourceForm;
|
||||
use Icinga\Forms\Config\Resource\LdapResourceForm;
|
||||
use Icinga\Forms\Config\Resource\LivestatusResourceForm;
|
||||
use Icinga\Forms\Config\Resource\SshResourceForm;
|
||||
use Icinga\Application\Platform;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
|
||||
@ -41,6 +42,8 @@ class ResourceConfigForm extends ConfigForm
|
||||
return new LivestatusResourceForm();
|
||||
} elseif ($type === 'file') {
|
||||
return new FileResourceForm();
|
||||
} elseif ($type === 'ssh') {
|
||||
return new SshResourceForm();
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf($this->translate('Invalid resource type "%s" provided'), $type));
|
||||
}
|
||||
@ -55,7 +58,7 @@ class ResourceConfigForm extends ConfigForm
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @thrwos InvalidArgumentException In case the resource does already exist
|
||||
* @throws InvalidArgumentException In case the resource does already exist
|
||||
*/
|
||||
public function add(array $values)
|
||||
{
|
||||
@ -116,6 +119,11 @@ class ResourceConfigForm extends ConfigForm
|
||||
}
|
||||
|
||||
$resourceConfig = $this->config->getSection($name);
|
||||
$resourceForm = $this->getResourceForm($resourceConfig->type);
|
||||
if (method_exists($resourceForm, 'beforeRemove')) {
|
||||
$resourceForm::beforeRemove($resourceConfig);
|
||||
}
|
||||
|
||||
$this->config->removeSection($name);
|
||||
return $resourceConfig;
|
||||
}
|
||||
@ -130,8 +138,9 @@ class ResourceConfigForm extends ConfigForm
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
$resourceForm = $this->getResourceForm($this->getElement('type')->getValue());
|
||||
|
||||
if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) {
|
||||
$resourceForm = $this->getResourceForm($this->getElement('type')->getValue());
|
||||
if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm::isValidResource($this)) {
|
||||
$this->addElement($this->getForceCreationCheckbox());
|
||||
return false;
|
||||
@ -141,6 +150,11 @@ class ResourceConfigForm extends ConfigForm
|
||||
$resource = $this->request->getQuery('resource');
|
||||
try {
|
||||
if ($resource === null) { // create new resource
|
||||
if (method_exists($resourceForm, 'beforeAdd')) {
|
||||
if (! $resourceForm::beforeAdd($this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->add($this->getValues());
|
||||
$message = $this->translate('Resource "%s" has been successfully created');
|
||||
} else { // edit existing resource
|
||||
@ -212,6 +226,7 @@ class ResourceConfigForm extends ConfigForm
|
||||
$resourceTypes = array(
|
||||
'file' => $this->translate('File'),
|
||||
'livestatus' => 'Livestatus',
|
||||
'ssh' => $this->translate('SSH Identity'),
|
||||
);
|
||||
if ($resourceType === 'ldap' || Platform::extensionLoaded('ldap')) {
|
||||
$resourceTypes['ldap'] = 'LDAP';
|
||||
|
@ -8,7 +8,7 @@ use Icinga\Web\Form;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\AuthenticationException;
|
||||
use Icinga\Authentication\User\LdapUserBackend;
|
||||
use Icinga\Authentication\User\UserBackend;
|
||||
|
||||
/**
|
||||
* Form class for adding/modifying LDAP user backends
|
||||
@ -48,6 +48,8 @@ class LdapBackendForm extends Form
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$isAd = isset($formData['type']) ? $formData['type'] === 'msldap' : false;
|
||||
|
||||
$this->addElement(
|
||||
'text',
|
||||
'name',
|
||||
@ -77,10 +79,13 @@ class LdapBackendForm extends Form
|
||||
'text',
|
||||
'user_class',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('LDAP User Object Class'),
|
||||
'description' => $this->translate('The object class used for storing users on the LDAP server.'),
|
||||
'value' => 'inetOrgPerson'
|
||||
'preserveDefault' => true,
|
||||
'required' => ! $isAd,
|
||||
'ignore' => $isAd,
|
||||
'disabled' => $isAd ?: null,
|
||||
'label' => $this->translate('LDAP User Object Class'),
|
||||
'description' => $this->translate('The object class used for storing users on the LDAP server.'),
|
||||
'value' => $isAd ? 'user' : 'inetOrgPerson'
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
@ -117,12 +122,15 @@ class LdapBackendForm extends Form
|
||||
'text',
|
||||
'user_name_attribute',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('LDAP User Name Attribute'),
|
||||
'description' => $this->translate(
|
||||
'preserveDefault' => true,
|
||||
'required' => ! $isAd,
|
||||
'ignore' => $isAd,
|
||||
'disabled' => $isAd ?: null,
|
||||
'label' => $this->translate('LDAP User Name Attribute'),
|
||||
'description' => $this->translate(
|
||||
'The attribute name used for storing the user name on the LDAP server.'
|
||||
),
|
||||
'value' => 'uid'
|
||||
'value' => $isAd ? 'sAMAccountName' : 'uid'
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
@ -130,7 +138,7 @@ class LdapBackendForm extends Form
|
||||
'backend',
|
||||
array(
|
||||
'disabled' => true,
|
||||
'value' => 'ldap'
|
||||
'value' => $isAd ? 'msldap' : 'ldap'
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
@ -170,8 +178,7 @@ class LdapBackendForm extends Form
|
||||
public static function isValidUserBackend(Form $form)
|
||||
{
|
||||
try {
|
||||
$ldapUserBackend = new LdapUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
|
||||
$ldapUserBackend->setConfig(new ConfigObject($form->getValues()));
|
||||
$ldapUserBackend = UserBackend::create(null, new ConfigObject($form->getValues()));
|
||||
$ldapUserBackend->assertAuthenticationPossible();
|
||||
} catch (AuthenticationException $e) {
|
||||
if (($previous = $e->getPrevious()) !== null) {
|
||||
@ -193,6 +200,8 @@ class LdapBackendForm extends Form
|
||||
* Return the configuration for the chosen resource
|
||||
*
|
||||
* @return ConfigObject
|
||||
*
|
||||
* @todo Check whether it's possible to drop this (Or even all occurences!)
|
||||
*/
|
||||
public function getResourceConfig()
|
||||
{
|
||||
|
@ -60,16 +60,24 @@ class UserBackendConfigForm extends ConfigForm
|
||||
*/
|
||||
public function getBackendForm($type)
|
||||
{
|
||||
if ($type === 'db') {
|
||||
$form = new DbBackendForm();
|
||||
$form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array());
|
||||
} elseif ($type === 'ldap') {
|
||||
$form = new LdapBackendForm();
|
||||
$form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array());
|
||||
} elseif ($type === 'external') {
|
||||
$form = new ExternalBackendForm();
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type));
|
||||
switch ($type)
|
||||
{
|
||||
case 'db':
|
||||
$form = new DbBackendForm();
|
||||
$form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array());
|
||||
break;
|
||||
case 'ldap':
|
||||
case 'msldap':
|
||||
$form = new LdapBackendForm();
|
||||
$form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array());
|
||||
break;
|
||||
case 'external':
|
||||
$form = new ExternalBackendForm();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(
|
||||
sprintf($this->translate('Invalid backend type "%s" provided'), $type)
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
@ -296,6 +304,7 @@ class UserBackendConfigForm extends ConfigForm
|
||||
}
|
||||
if (isset($this->resources['ldap']) && ($backendType === 'ldap' || Platform::extensionLoaded('ldap'))) {
|
||||
$backendTypes['ldap'] = 'LDAP';
|
||||
$backendTypes['msldap'] = 'ActiveDirectory';
|
||||
}
|
||||
|
||||
$externalBackends = array_filter(
|
||||
|
310
application/forms/Config/UserGroup/LdapUserGroupBackendForm.php
Normal file
310
application/forms/Config/UserGroup/LdapUserGroupBackendForm.php
Normal file
@ -0,0 +1,310 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Config\UserGroup;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Authentication\User\UserBackend;
|
||||
use Icinga\Authentication\UserGroup\LdapUserGroupBackend;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Protocol\Ldap\Connection;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Notification;
|
||||
|
||||
/**
|
||||
* Form for managing LDAP user group backends
|
||||
*/
|
||||
class LdapUserGroupBackendForm extends Form
|
||||
{
|
||||
/**
|
||||
* Initialize this form
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->setName('form_config_ldapusergroupbackend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add elements to this form
|
||||
*
|
||||
* @param array $formData
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$resourceNames = $this->getLdapResourceNames();
|
||||
$this->addElement(
|
||||
'select',
|
||||
'resource',
|
||||
array(
|
||||
'required' => true,
|
||||
'autosubmit' => true,
|
||||
'label' => $this->translate('LDAP Connection'),
|
||||
'description' => $this->translate('The LDAP connection to use for this backend.'),
|
||||
'multiOptions' => array_combine($resourceNames, $resourceNames)
|
||||
)
|
||||
);
|
||||
$resource = ResourceFactory::create(
|
||||
isset($formData['resource']) && in_array($formData['resource'], $resourceNames)
|
||||
? $formData['resource']
|
||||
: $resourceNames[0]
|
||||
);
|
||||
|
||||
$userBackends = array('none' => $this->translate('None', 'usergroupbackend.ldap.user_backend'));
|
||||
$userBackendNames = $this->getLdapUserBackendNames($resource);
|
||||
if (! empty($userBackendNames)) {
|
||||
$userBackends = array_merge($userBackends, array_combine($userBackendNames, $userBackendNames));
|
||||
}
|
||||
$this->addElement(
|
||||
'select',
|
||||
'user_backend',
|
||||
array(
|
||||
'required' => true,
|
||||
'autosubmit' => true,
|
||||
'label' => $this->translate('User Backend'),
|
||||
'description' => $this->translate('The user backend to link with this user group backend.'),
|
||||
'multiOptions' => $userBackends
|
||||
)
|
||||
);
|
||||
|
||||
$groupBackend = new LdapUserGroupBackend($resource);
|
||||
if ($formData['type'] === 'ldap') {
|
||||
$defaults = $groupBackend->getOpenLdapDefaults();
|
||||
$groupConfigDisabled = $userConfigDisabled = null; // MUST BE null, do NOT change this to false!
|
||||
} else { // $formData['type'] === 'msldap'
|
||||
$defaults = $groupBackend->getActiveDirectoryDefaults();
|
||||
$groupConfigDisabled = $userConfigDisabled = true;
|
||||
}
|
||||
|
||||
$dnDisabled = null; // MUST BE null
|
||||
if (isset($formData['user_backend']) && $formData['user_backend'] !== 'none') {
|
||||
$userBackend = UserBackend::create($formData['user_backend']);
|
||||
$defaults->merge(array(
|
||||
'user_base_dn' => $userBackend->getBaseDn(),
|
||||
'user_class' => $userBackend->getUserClass(),
|
||||
'user_name_attribute' => $userBackend->getUserNameAttribute(),
|
||||
'user_filter' => $userBackend->getFilter()
|
||||
));
|
||||
$userConfigDisabled = $dnDisabled = true;
|
||||
}
|
||||
|
||||
$this->createGroupConfigElements($defaults, $groupConfigDisabled);
|
||||
$this->createUserConfigElements($defaults, $userConfigDisabled, $dnDisabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add all elements to this form required for the group configuration
|
||||
*
|
||||
* @param ConfigObject $defaults
|
||||
* @param null|bool $disabled
|
||||
*/
|
||||
protected function createGroupConfigElements(ConfigObject $defaults, $disabled)
|
||||
{
|
||||
$this->addElement(
|
||||
'text',
|
||||
'group_class',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'ignore' => $disabled,
|
||||
'disabled' => $disabled,
|
||||
'label' => $this->translate('LDAP Group Object Class'),
|
||||
'description' => $this->translate('The object class used for storing groups on the LDAP server.'),
|
||||
'value' => $defaults->group_class
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'group_filter',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'allowEmpty' => true,
|
||||
'label' => $this->translate('LDAP Group Filter'),
|
||||
'description' => $this->translate(
|
||||
'An additional filter to use when looking up groups using the specified connection. '
|
||||
. 'Leave empty to not to use any additional filter rules.'
|
||||
),
|
||||
'requirement' => $this->translate(
|
||||
'The filter needs to be expressed as standard LDAP expression, without'
|
||||
. ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
|
||||
),
|
||||
'validators' => array(
|
||||
array(
|
||||
'Callback',
|
||||
false,
|
||||
array(
|
||||
'callback' => function ($v) {
|
||||
return strpos($v, '(') !== 0;
|
||||
},
|
||||
'messages' => array(
|
||||
'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
'value' => $defaults->group_filter
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'group_name_attribute',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'ignore' => $disabled,
|
||||
'disabled' => $disabled,
|
||||
'label' => $this->translate('LDAP Group Name Attribute'),
|
||||
'description' => $this->translate(
|
||||
'The attribute name used for storing a group\'s name on the LDAP server.'
|
||||
),
|
||||
'value' => $defaults->group_name_attribute
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'base_dn',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'label' => $this->translate('LDAP Group Base DN'),
|
||||
'description' => $this->translate(
|
||||
'The path where groups can be found on the LDAP server. Leave ' .
|
||||
'empty to select all users available using the specified connection.'
|
||||
),
|
||||
'value' => $defaults->base_dn
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add all elements to this form required for the user configuration
|
||||
*
|
||||
* @param ConfigObject $defaults
|
||||
* @param null|bool $disabled
|
||||
* @param null|bool $dnDisabled
|
||||
*/
|
||||
protected function createUserConfigElements(ConfigObject $defaults, $disabled, $dnDisabled)
|
||||
{
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user_class',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'ignore' => $disabled,
|
||||
'disabled' => $disabled,
|
||||
'label' => $this->translate('LDAP User Object Class'),
|
||||
'description' => $this->translate('The object class used for storing users on the LDAP server.'),
|
||||
'value' => $defaults->user_class
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user_filter',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'allowEmpty' => true,
|
||||
'ignore' => $dnDisabled,
|
||||
'disabled' => $dnDisabled,
|
||||
'label' => $this->translate('LDAP User Filter'),
|
||||
'description' => $this->translate(
|
||||
'An additional filter to use when looking up users using the specified connection. '
|
||||
. 'Leave empty to not to use any additional filter rules.'
|
||||
),
|
||||
'requirement' => $this->translate(
|
||||
'The filter needs to be expressed as standard LDAP expression, without'
|
||||
. ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
|
||||
),
|
||||
'validators' => array(
|
||||
array(
|
||||
'Callback',
|
||||
false,
|
||||
array(
|
||||
'callback' => function ($v) {
|
||||
return strpos($v, '(') !== 0;
|
||||
},
|
||||
'messages' => array(
|
||||
'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
'value' => $defaults->user_filter
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user_name_attribute',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'ignore' => $disabled,
|
||||
'disabled' => $disabled,
|
||||
'label' => $this->translate('LDAP User Name Attribute'),
|
||||
'description' => $this->translate(
|
||||
'The attribute name used for storing a user\'s name on the LDAP server.'
|
||||
),
|
||||
'value' => $defaults->user_name_attribute
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user_base_dn',
|
||||
array(
|
||||
'preserveDefault' => true,
|
||||
'ignore' => $dnDisabled,
|
||||
'disabled' => $dnDisabled,
|
||||
'label' => $this->translate('LDAP User Base DN'),
|
||||
'description' => $this->translate(
|
||||
'The path where users can be found on the LDAP server. Leave ' .
|
||||
'empty to select all users available using the specified connection.'
|
||||
),
|
||||
'value' => $defaults->user_base_dn
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of all configured LDAP resources
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getLdapResourceNames()
|
||||
{
|
||||
$names = array();
|
||||
foreach (ResourceFactory::getResourceConfigs() as $name => $config) {
|
||||
if (in_array(strtolower($config->type), array('ldap', 'msldap'))) {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($names)) {
|
||||
Notification::error(
|
||||
$this->translate('No LDAP resources available. Please configure an LDAP resource first.')
|
||||
);
|
||||
$this->getResponse()->redirectAndExit('config/createresource');
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of all configured LDAP user backends
|
||||
*
|
||||
* @param Connection $resource
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getLdapUserBackendNames(Connection $resource)
|
||||
{
|
||||
$names = array();
|
||||
foreach (Config::app('authentication') as $name => $config) {
|
||||
if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) {
|
||||
$backendResource = ResourceFactory::create($config->resource);
|
||||
if (
|
||||
$backendResource->getHostname() === $resource->getHostname()
|
||||
&& $backendResource->getPort() === $resource->getPort()
|
||||
) {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
}
|
@ -13,6 +13,13 @@ use Icinga\Forms\ConfigForm;
|
||||
*/
|
||||
class UserGroupBackendForm extends ConfigForm
|
||||
{
|
||||
/**
|
||||
* The backend to load when displaying the form for the first time
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $backendToLoad;
|
||||
|
||||
/**
|
||||
* Initialize this form
|
||||
*/
|
||||
@ -31,10 +38,17 @@ class UserGroupBackendForm extends ConfigForm
|
||||
*/
|
||||
public function getBackendForm($type)
|
||||
{
|
||||
if ($type === 'db') {
|
||||
return new DbUserGroupBackendForm();
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type));
|
||||
switch ($type)
|
||||
{
|
||||
case 'db':
|
||||
return new DbUserGroupBackendForm();
|
||||
case 'ldap':
|
||||
case 'msldap':
|
||||
return new LdapUserGroupBackendForm();
|
||||
default:
|
||||
throw new InvalidArgumentException(
|
||||
sprintf($this->translate('Invalid backend type "%s" provided'), $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,10 +67,7 @@ class UserGroupBackendForm extends ConfigForm
|
||||
throw new NotFoundError('No user group backend called "%s" found', $name);
|
||||
}
|
||||
|
||||
$data = $this->config->getSection($name)->toArray();
|
||||
$data['type'] = $data['backend'];
|
||||
$data['name'] = $name;
|
||||
$this->populate($data);
|
||||
$this->backendToLoad = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -103,13 +114,23 @@ class UserGroupBackendForm extends ConfigForm
|
||||
}
|
||||
|
||||
$backendConfig = $this->config->getSection($name);
|
||||
if (isset($data['name']) && $data['name'] !== $name) {
|
||||
$this->config->removeSection($name);
|
||||
$name = $data['name'];
|
||||
if (isset($data['name'])) {
|
||||
if ($data['name'] !== $name) {
|
||||
$this->config->removeSection($name);
|
||||
$name = $data['name'];
|
||||
}
|
||||
|
||||
unset($data['name']);
|
||||
}
|
||||
|
||||
$this->config->setSection($name, $backendConfig->merge($data));
|
||||
$backendConfig->merge($data);
|
||||
foreach ($backendConfig->toArray() as $k => $v) {
|
||||
if ($v === null) {
|
||||
unset($backendConfig->$k);
|
||||
}
|
||||
}
|
||||
|
||||
$this->config->setSection($name, $backendConfig);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -161,7 +182,9 @@ class UserGroupBackendForm extends ConfigForm
|
||||
|
||||
// TODO(jom): We did not think about how to configure custom group backends yet!
|
||||
$backendTypes = array(
|
||||
'db' => $this->translate('Database')
|
||||
'db' => $this->translate('Database'),
|
||||
'ldap' => 'LDAP',
|
||||
'msldap' => 'ActiveDirectory'
|
||||
);
|
||||
|
||||
$backendType = isset($formData['type']) ? $formData['type'] : null;
|
||||
@ -191,8 +214,34 @@ class UserGroupBackendForm extends ConfigForm
|
||||
)
|
||||
);
|
||||
|
||||
$backendForm = $this->getBackendForm($backendType);
|
||||
$backendForm->createElements($formData);
|
||||
$this->addElements($backendForm->getElements());
|
||||
$this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the configuration of the backend to load
|
||||
*/
|
||||
public function onRequest()
|
||||
{
|
||||
if ($this->backendToLoad) {
|
||||
$data = $this->config->getSection($this->backendToLoad)->toArray();
|
||||
$data['type'] = $data['backend'];
|
||||
$data['name'] = $this->backendToLoad;
|
||||
$this->populate($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['backend_form']);
|
||||
unset($values['backend_form']);
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
25
application/views/scripts/about/index.phtml
Normal file
25
application/views/scripts/about/index.phtml
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="content">
|
||||
<h1>Icinga Web 2</h1>
|
||||
<?php
|
||||
$versionInfo = array();
|
||||
if ($version !== false) {
|
||||
foreach (array(
|
||||
'appVersion' => $this->translate('Version: %s'),
|
||||
'gitCommitID' => $this->translate('Git commit ID: %s'),
|
||||
'gitCommitDate' => $this->translate('Git commit date: %s')
|
||||
) as $key => $label) {
|
||||
if (array_key_exists($key, $version) && null !== ($value = $version[$key])) {
|
||||
$versionInfo[] = sprintf($label, htmlspecialchars($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo (
|
||||
0 === count($versionInfo)
|
||||
? '<p class="message-error">' . $this->translate(
|
||||
'Can\'t determine Icinga Web 2\'s version'
|
||||
)
|
||||
: '<p>' . nl2br(implode("\n", $versionInfo), false)
|
||||
) . '</p>';
|
||||
?>
|
||||
</div>
|
@ -18,7 +18,7 @@ if (! $this->compact): ?>
|
||||
<div class="content groups">
|
||||
<?php
|
||||
|
||||
if ($backend === null) {
|
||||
if (! isset($backend)) {
|
||||
echo $this->translate('No backend found which is able to list groups') . '</div>';
|
||||
return;
|
||||
} else {
|
||||
|
@ -31,15 +31,16 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
|
||||
<p class="group-name"><strong><?= $this->escape($group->group_name); ?></strong></p> <?= $editLink; ?>
|
||||
<p class="group-created"><strong><?= $this->translate('Created at'); ?>:</strong> <?= $group->created_at === null ? '-' : $this->formatDateTime($group->created_at); ?></p>
|
||||
<p class="group-modified"><strong><?= $this->translate('Last modified'); ?>:</strong> <?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></p>
|
||||
<h3><?= $this->translate('Members'); ?></h3>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="content members" data-base-target="_next">
|
||||
<?php if (count($members) > 0): ?>
|
||||
|
@ -67,7 +67,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
<a data-base-target="_next" href="<?= $this->href('role/new') ?>">
|
||||
<a data-base-target="_next" href="<?= $this->href('role/add') ?>">
|
||||
<?= $this->translate('Create a New Role') ?>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ if (! $this->compact): ?>
|
||||
<div class="content users">
|
||||
<?php
|
||||
|
||||
if ($backend === null) {
|
||||
if (! isset($backend)) {
|
||||
echo $this->translate('No backend found which is able to list users') . '</div>';
|
||||
return;
|
||||
} else {
|
||||
|
@ -31,15 +31,16 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
|
||||
<p class="user-state"><strong><?= $this->translate('State'); ?>:</strong> <?= $user->is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?></p>
|
||||
<p class="user-created"><strong><?= $this->translate('Created at'); ?>:</strong> <?= $user->created_at === null ? '-' : $this->formatDateTime($user->created_at); ?></p>
|
||||
<p class="user-modified"><strong><?= $this->translate('Last modified'); ?>:</strong> <?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></p>
|
||||
<h3><?= $this->translate('Group Memberships'); ?></h3>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="content memberships" data-base-target="_next">
|
||||
<?php if (count($memberships) > 0): ?>
|
||||
|
70
doc/about.md
Normal file
70
doc/about.md
Normal file
@ -0,0 +1,70 @@
|
||||
# <a id="about"></a> About Icinga Web 2
|
||||
|
||||
Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design.
|
||||
It's fast, responsive, accessible and easily extensible with modules.
|
||||
|
||||
## <a id="about-monitoring"></a> The monitoring module
|
||||
|
||||
This is the core module for most Icinga Web 2 users.
|
||||
|
||||
It provides an intuitive user interface for monitoring with Icinga (1 and 2).
|
||||
Especially there are lots of list and detail views (e.g. for hosts and services)
|
||||
you can sort and filter depending on what you want to see.
|
||||
|
||||
You can also control the monitoring process itself by sending external commands to Icinga.
|
||||
Most such actions (like rescheduling a check) can be done with just a single click.
|
||||
|
||||
## <a id="about-installation"></a> Installation
|
||||
|
||||
Icinga Web 2 can be installed easily from packages from the official package repositories.
|
||||
Setting it up is also easy with the web based setup wizard.
|
||||
|
||||
See [here](installation#installation) for more information about the installation.
|
||||
|
||||
## <a id="about-configuration"></a> Configuration
|
||||
|
||||
Icinga Web 2 can be configured via the user interface and .ini files.
|
||||
|
||||
See [here](configuration#configuration) for more information about the configuration.
|
||||
|
||||
## <a id="about-authentication"></a> Authentication
|
||||
|
||||
With Icinga Web 2 you can authenticate against relational databases, LDAP and more.
|
||||
These authentication methods can be easily configured (via the corresponding .ini file).
|
||||
|
||||
See [here](authentication#authentication) for more information about
|
||||
the different authentication methods available and how to configure them.
|
||||
|
||||
## <a id="about-authorization"></a> Authorization
|
||||
|
||||
In Icinga Web 2 there are permissions and restrictions to allow and deny (respectively)
|
||||
roles to view or to do certain things.
|
||||
These roles can be assigned to users and groups.
|
||||
|
||||
See [here](security#security) for more information about authorization
|
||||
and how to configure roles.
|
||||
|
||||
## <a id="about-preferences"></a> User preferences
|
||||
|
||||
Besides the global configuration each user has individual configuration options
|
||||
like the interface's language or the current timezone.
|
||||
They can be stored either in a database or in .ini files.
|
||||
|
||||
See [here](preferences#preferences) for more information about a user's preferences
|
||||
and how to configure their storage type.
|
||||
|
||||
## <a id="about-documentation"></a> Documentation
|
||||
|
||||
With the documentation module you can read the documentation of the framework (and any module) directly in the user interface.
|
||||
|
||||
The module can also export the documentation to PDF.
|
||||
|
||||
## <a id="about-translation"></a> Translation
|
||||
|
||||
With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choice.
|
||||
|
||||
Currently provided languages:
|
||||
|
||||
* German
|
||||
* Italian
|
||||
* Portuguese
|
@ -8,7 +8,7 @@ different files, when the information about a data source changes.
|
||||
|
||||
Each section in **config/resources.ini** represents a data source with the section name being the identifier used to
|
||||
reference this specific data source. Depending on the data source type, the sections define different directives.
|
||||
The available data source types are *db*, *ldap* and *livestatus* which will described in detail in the following
|
||||
The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following
|
||||
paragraphs.
|
||||
|
||||
### <a id="resources-configuration-database"></a> Database
|
||||
@ -64,6 +64,26 @@ bind_dn = "cn=admin,ou=people,dc=icinga,dc=org"
|
||||
bind_pw = admin`
|
||||
````
|
||||
|
||||
### <a id="resources-configuration-ssh"></a> SSH
|
||||
|
||||
A SSH resource contains the information about the user and the private key location, which can be used for the key-based
|
||||
ssh authentication.
|
||||
|
||||
Directive | Description
|
||||
--------------------|------------
|
||||
**type** | `ssh`
|
||||
**user** | The username to use when connecting to the server.
|
||||
**private_key** | The path to the private key of the user.
|
||||
|
||||
**Example:**
|
||||
|
||||
````
|
||||
[ssh]
|
||||
type = "ssh"
|
||||
user = "ssh-user"
|
||||
private_key = "/etc/icingaweb2/ssh/ssh-user"
|
||||
````
|
||||
|
||||
### <a id="resources-configuration-livestatus"></a> Livestatus
|
||||
|
||||
A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data.
|
||||
|
279
doc/security.md
Normal file
279
doc/security.md
Normal file
@ -0,0 +1,279 @@
|
||||
# <a id="security"></a> Security
|
||||
|
||||
Access control is a vital part of configuring Icinga Web 2 in a secure way.
|
||||
It is important that not every user that has access to Icinga Web 2 is able
|
||||
to do any action or to see any host and service. For example, it is useful to allow
|
||||
only a small group of administrators to change the Icinga Web 2 configuration,
|
||||
to prevent misconfiguration or security breaches. Another important use case is
|
||||
creating groups of users which can only see the fraction of the monitoring
|
||||
environment they are in charge of.
|
||||
|
||||
This chapter will describe how to do the security configuration of Icinga Web 2
|
||||
and how to apply permissions and restrictions to users or groups of users.
|
||||
|
||||
## Basics
|
||||
|
||||
Icinga Web 2 access control is done by defining **roles** that associate permissions
|
||||
and restrictions with **users** and **groups**. There are two general kinds of
|
||||
things to which access can be managed: actions and objects.
|
||||
|
||||
|
||||
### Actions
|
||||
|
||||
Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration,
|
||||
changing permissions or sending a command to the Icinga instance through the
|
||||
<a href="http://docs.icinga.org/icinga2/latest/doc/module/icinga2/toc#!/icinga2/latest/doc/module/icinga2/chapter/getting-started#setting-up-external-command-pipe">Command Pipe</a>
|
||||
in the monitoring module. All actions must be be **allowed explicitly** using permissions.
|
||||
|
||||
A permission is a simple list of identifiers of actions a user is
|
||||
allowed to do. Permissions are described in greater detail in the
|
||||
section [Permissions](#permissions).
|
||||
|
||||
### Objects
|
||||
|
||||
There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events.
|
||||
|
||||
By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using restrictions.
|
||||
|
||||
Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described
|
||||
in greater detail in the section [Restrictions](#restrictions).
|
||||
|
||||
### Users
|
||||
|
||||
Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the
|
||||
**user name** used during login.
|
||||
For example, there might be user called **jdoe** authenticated
|
||||
using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend.
|
||||
In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**.
|
||||
|
||||
Icinga Web 2 users and groups are not configured by a configuration file, but provided by
|
||||
an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication).
|
||||
|
||||
|
||||
<div class="info-box">
|
||||
Since Icinga Web 2, users in the Icinga configuration and the web authentication are separated, to allow
|
||||
use of external authentication providers. This means that users and groups defined in the Icinga configuration are not available to Icinga Web 2. Instead it uses its own authentication
|
||||
backend to fetch users and groups from, which must be configured separately.
|
||||
</div>
|
||||
|
||||
#### Managing Users
|
||||
|
||||
When using a [Database
|
||||
as authentication backend](authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration
|
||||
can be found at **Configuration > Authentication > Users **.
|
||||
|
||||
### Groups
|
||||
|
||||
If there is a big amount of users to manage, it would be tedious to specify each user
|
||||
separately when regularly referring to the same group of users. Because of that, it is possible to group users.
|
||||
A user can be member of multiple groups and will inherit all permissions and restrictions.
|
||||
|
||||
Like users, groups are identified solely by their **name** that is provided by
|
||||
a **group backend**. For extended information on setting up group backends,
|
||||
please read the chapter [Authentication](authentication.md#authentication).
|
||||
|
||||
|
||||
#### Managing Groups
|
||||
|
||||
When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication),
|
||||
it is possible to manage groups and group memberships directly in the frontend. This configuration
|
||||
can be found at **Configuration > Authentication > Groups **.
|
||||
|
||||
## Roles
|
||||
|
||||
A role defines a set of **permissions** and **restrictions** and assigns
|
||||
those to **users** and **groups**. For example, a role **admins** could define that certain
|
||||
users have access to all configuration options, or another role **support**
|
||||
could define that a list of users or groups is restricted to see only hosts and services
|
||||
that match a specific query.
|
||||
|
||||
The actual permission of a certain user will be determined by merging the permissions
|
||||
and restrictions of the user itself and all the groups the user is member of. Permissions can
|
||||
be simply added up, while restrictions follow a slighty more complex pattern, that is described
|
||||
in the section [Stacking Filters](#stacking-filters).
|
||||
|
||||
### Configuration
|
||||
|
||||
Roles can be changed either through the icingaweb2 interface, by navigation
|
||||
to the page **Configuration > Authentication > Roles**, or through editing the
|
||||
configuration file:
|
||||
|
||||
|
||||
/etc/icingaweb2/roles.ini
|
||||
|
||||
|
||||
#### Introducing Example
|
||||
|
||||
To get you a quick start, here is an example of what a role definition could look like:
|
||||
|
||||
|
||||
[winadmin]
|
||||
users = "jdoe, janedoe"
|
||||
groups = "admin"
|
||||
permissions = "config/application/*, monitoring/commands/schedule-check"
|
||||
monitoring/filter/objects = "host=*win*"
|
||||
|
||||
|
||||
This example creates a role called **winadmin**, that grants all permissions in `config/application/*` and `monitoring/commands/schedule-check` and additionally only
|
||||
allows the hosts and services that match the filter `host=*win*` to be displayed. The users
|
||||
**jdoe** and **janedoe** and all members of the group **admin** will be affected
|
||||
by this role.
|
||||
|
||||
|
||||
#### <a id="syntax"></a> Syntax
|
||||
|
||||
Each role is defined as a section, with the name of the role as section name. The following
|
||||
attributes can be defined for each role in a default Icinga Web 2 installation:
|
||||
|
||||
|
||||
Directive | Description
|
||||
---------------------------|-----------------------------------------------------------------------------
|
||||
users | A comma-separated list of user **user names** that are affected by this role
|
||||
groups | A comma-separated list of **group names** that are affected by this role
|
||||
permissions | A comma-separated list of **permissions** granted by this role
|
||||
monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts
|
||||
|
||||
|
||||
|
||||
## <a id="permissions"></a> Permissions
|
||||
|
||||
Permissions can be used to allow users or groups certain **actions**. By default,
|
||||
all actions are **prohibited** and must be allowed explicitly by a role for any user.
|
||||
|
||||
Each action in Icinga Web 2 is denoted by a **namespaced key**, which is used to order and
|
||||
group those actions. All actions that affect the configuration of Icinga Web 2, are in a
|
||||
namespace called **config**, while all configurations that affect authentication
|
||||
are in the namespace `config/authentication`
|
||||
|
||||
**Wildcards** can be used to grant permission for all actions in a certain namespace.
|
||||
The permission `config/*` would grant permission to all configuration actions,
|
||||
while just specifying a wildcard `*` would give permission for all actions.
|
||||
|
||||
When multiple roles assign permissions to the same user (either directly or indirectly
|
||||
through a group) all permissions can simply be added together to get the users actual permission set.
|
||||
|
||||
#### Global permissions
|
||||
|
||||
Name | Permits
|
||||
-------------------------------------|-----------------------------------------------------------------
|
||||
* | Allow everything, including module-specific permissions
|
||||
config/* | Allow all configuration actions
|
||||
config/application/* | Allow configuring IcingaWeb2
|
||||
config/application/general | Allow general settings, like logging or preferences
|
||||
config/application/resources | Allow changing resources for retrieving data
|
||||
config/application/userbackend | Allow changing backends for retrieving available users
|
||||
config/application/usergroupbackend | Allow changing backends for retrieving available groups
|
||||
config/authentication/* | Allow configuring IcingaWeb2 authentication mechanisms
|
||||
config/authentication/users/* | Allow all user actions
|
||||
config/authentication/users/show | Allow displaying avilable users
|
||||
config/authentication/users/add | Allow adding a new user to the backend
|
||||
config/authentication/users/edit | Allow editing an existing user in the backend
|
||||
config/authentication/users/remove | Allow removing an existing user from the backend
|
||||
config/authentication/groups/* | Allow all group actions
|
||||
config/authentication/groups/show | Allow displaying all available groups
|
||||
config/authentication/groups/add | Allow adding a new group to the backend
|
||||
config/authentication/groups/edit | Allow editing existing groups in a backend
|
||||
config/authentication/groups/remove | Allow removing existing groups from the backend
|
||||
config/authentication/roles/* | Allow all role actions
|
||||
config/authentication/roles/add | Allow adding a new role
|
||||
config/authentication/roles/show | Allow displaying available roles
|
||||
config/authentication/roles/edit | Allow changing an existing role
|
||||
config/authentication/roles/remove | Allow removing an existing row
|
||||
config/modules | Allow enabling or disabling modules
|
||||
|
||||
|
||||
#### Monitoring module permissions
|
||||
|
||||
The built-in monitoring module defines an additional set of permissions, that
|
||||
is described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security).
|
||||
|
||||
|
||||
## <a id="restrictions"></a> Restrictions
|
||||
|
||||
Restrictions can be used to define what a user or group can see by specifying
|
||||
a filter expression that applies to a defined set of data. By default, when no
|
||||
restrictions are defined, a user will be able to see every information that is available.
|
||||
|
||||
A restrictions is always specified for a certain **filter directive**, that defines what
|
||||
data the filter is applied to. The **filter directive** is a simple identifier, that was
|
||||
defined in an Icinga Web 2 module. The only filter directive that is available
|
||||
in a default installation, is the `monitoring/filter/objects` directive, defined by the monitoring module,
|
||||
that can be used to apply filter to hosts and services. This directive was previously
|
||||
mentioned in the section [Syntax](#syntax).
|
||||
|
||||
### Filter Expressions
|
||||
|
||||
Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in
|
||||
the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions).
|
||||
|
||||
Any filter expression that is allowed in the filtered view, is also an allowed filter expression.
|
||||
This means, that it is possible to define negations, wildcards, and even nested
|
||||
filter expressions containing AND and OR-Clauses.
|
||||
|
||||
The filter expression will be **implicitly** added as an **AND-Clause** to each query on
|
||||
the filtered data. The following shows the filter expression `host=*win*` being applied on `monitoring/filter/objects`.
|
||||
|
||||
|
||||
Regular filter query:
|
||||
|
||||
AND-- service_problem = 1
|
||||
|
|
||||
+--- service_handled = 0
|
||||
|
||||
|
||||
With our restriction applied, any user affected by this restrictions will see the
|
||||
results of this query instead:
|
||||
|
||||
|
||||
AND-- host = *win*
|
||||
|
|
||||
+--AND-- service_problem = 1
|
||||
|
|
||||
+--- service_handled = 0
|
||||
|
||||
|
||||
#### <a id="stacking-filters"></a> Stacking Filters
|
||||
|
||||
When multiple roles assign restrictions to the same user, either directly or indirectly
|
||||
through a group, all filters will be combined using an **OR-Clause**, resulting in the final
|
||||
expression:
|
||||
|
||||
|
||||
AND-- OR-- $FILTER1
|
||||
| |
|
||||
| +-- $FILTER2
|
||||
| |
|
||||
| +-- $FILTER3
|
||||
|
|
||||
+--AND-- service_problem = 1
|
||||
|
|
||||
+--- service_handled = 0
|
||||
|
||||
|
||||
As a result, a user is be able to see hosts that are matched by **ANY** of
|
||||
the filter expressions. The following examples will show the usefulness of this behavior:
|
||||
|
||||
#### Example 1: Negation
|
||||
|
||||
[winadmin]
|
||||
groups = "windows-admins"
|
||||
monitoring/filter/objects = "host=*win*"
|
||||
|
||||
Will display only hosts and services whose host name contains **win**.
|
||||
|
||||
[webadmin]
|
||||
groups = "web-admins"
|
||||
monitoring/filter/objects = "host!=*win*"
|
||||
|
||||
Will only match hosts and services whose host name does **not** contain **win**
|
||||
|
||||
Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services.
|
||||
|
||||
#### Example 2: Hostgroups
|
||||
|
||||
[unix-server]
|
||||
groups = "unix-admins"
|
||||
monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)"
|
||||
|
||||
This role allows all members of the group unix-admins to see hosts and services
|
||||
that are part of the host-group linux-servers or the host-group bsd-servers.
|
@ -201,7 +201,7 @@ cp -prv packages/files/config/modules/setup %{buildroot}/%{configdir}/modules/
|
||||
|
||||
%pre
|
||||
getent group icingacmd >/dev/null || groupadd -r icingacmd
|
||||
%if 0%{?suse_version}
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 01200
|
||||
usermod -A icingacmd,%{icingawebgroup} %{wwwuser}
|
||||
%else
|
||||
usermod -a -G icingacmd,%{icingawebgroup} %{wwwuser}
|
||||
@ -218,6 +218,7 @@ rm -rf %{buildroot}
|
||||
%{basedir}/application/forms
|
||||
%{basedir}/application/layouts
|
||||
%{basedir}/application/views
|
||||
%{basedir}/application/VERSION
|
||||
%{basedir}/doc
|
||||
%{basedir}/modules
|
||||
%{basedir}/public
|
||||
|
@ -134,18 +134,20 @@ abstract class ApplicationBootstrap
|
||||
$this->vendorDir = $baseDir . '/library/vendor';
|
||||
$this->libDir = realpath(__DIR__ . '/../..');
|
||||
|
||||
$this->setupAutoloader();
|
||||
|
||||
if ($configDir === null) {
|
||||
if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) {
|
||||
$configDir = $_SERVER['ICINGAWEB_CONFIGDIR'];
|
||||
} else {
|
||||
$configDir = '/etc/icingaweb2';
|
||||
$configDir = Platform::isWindows()
|
||||
? $baseDir . '/config'
|
||||
: '/etc/icingaweb2';
|
||||
}
|
||||
}
|
||||
$canonical = realpath($configDir);
|
||||
$this->configDir = $canonical ? $canonical : $configDir;
|
||||
|
||||
$this->setupAutoloader();
|
||||
|
||||
set_include_path(
|
||||
implode(
|
||||
PATH_SEPARATOR,
|
||||
|
@ -59,6 +59,122 @@ class Platform
|
||||
return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Linux distribution's name
|
||||
* or 'linux' if the name could not be found out
|
||||
* or false if the OS isn't Linux or an error occurred
|
||||
*
|
||||
* @param int $reliable
|
||||
* 3: Only parse /etc/os-release (or /usr/lib/os-release).
|
||||
* For the paranoid ones.
|
||||
* 2: If that (3) doesn't help, check /etc/*-release, too.
|
||||
* If something is unclear, return 'linux'.
|
||||
* 1: Almost equal to mode 2. The possible return values also include:
|
||||
* 'redhat' -- unclear whether RHEL/Fedora/...
|
||||
* 'suse' -- unclear whether SLES/openSUSE/...
|
||||
* 0: If even that (1) doesn't help, check /proc/version, too.
|
||||
* This may not work (as expected) on LXC containers!
|
||||
* (No reliability at all!)
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function getLinuxDistro($reliable = 2)
|
||||
{
|
||||
if (! self::isLinux()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (array('/etc/os-release', '/usr/lib/os-release') as $osReleaseFile) {
|
||||
if (false === ($osRelease = @file(
|
||||
$osReleaseFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
|
||||
))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($osRelease as $osInfo) {
|
||||
if (false === ($res = @preg_match('/(?<!.)[ \t]*#/ms', $osInfo))) {
|
||||
return false;
|
||||
}
|
||||
if ($res === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
if (false === ($res = @preg_match(
|
||||
'/(?<!.)[ \t]*ID[ \t]*=[ \t]*(\'|"|)(.*?)(?:\1)[ \t]*(?!.)/msi',
|
||||
$osInfo,
|
||||
$matches
|
||||
))) {
|
||||
return false;
|
||||
}
|
||||
if (! ($res === 0 || $matches[2] === '' || $matches[2] === 'linux')) {
|
||||
return $matches[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($reliable > 2) {
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
foreach (array(
|
||||
'fedora' => '/etc/fedora-release',
|
||||
'centos' => '/etc/centos-release'
|
||||
) as $distro => $releaseFile) {
|
||||
if (! (false === (
|
||||
$release = @file_get_contents($releaseFile)
|
||||
) || false === strpos(strtolower($release), $distro))) {
|
||||
return $distro;
|
||||
}
|
||||
}
|
||||
|
||||
if (false !== ($release = @file_get_contents('/etc/redhat-release'))) {
|
||||
$release = strtolower($release);
|
||||
if (false !== strpos($release, 'red hat enterprise linux')) {
|
||||
return 'rhel';
|
||||
}
|
||||
foreach (array('fedora', 'centos') as $distro) {
|
||||
if (false !== strpos($release, $distro)) {
|
||||
return $distro;
|
||||
}
|
||||
}
|
||||
return $reliable < 2 ? 'redhat' : 'linux';
|
||||
}
|
||||
|
||||
if (false !== ($release = @file_get_contents('/etc/SuSE-release'))) {
|
||||
$release = strtolower($release);
|
||||
foreach (array(
|
||||
'opensuse' => 'opensuse',
|
||||
'sles' => 'suse linux enterprise server',
|
||||
'sled' => 'suse linux enterprise desktop'
|
||||
) as $distro => $name) {
|
||||
if (false !== strpos($release, $name)) {
|
||||
return $distro;
|
||||
}
|
||||
}
|
||||
return $reliable < 2 ? 'suse' : 'linux';
|
||||
}
|
||||
|
||||
if ($reliable < 1) {
|
||||
if (false === ($procVersion = @file_get_contents('/proc/version'))) {
|
||||
return false;
|
||||
}
|
||||
$procVersion = strtolower($procVersion);
|
||||
foreach (array(
|
||||
'redhat' => 'red hat',
|
||||
'suse' => 'suse linux',
|
||||
'ubuntu' => 'ubuntu',
|
||||
'debian' => 'debian'
|
||||
) as $distro => $name) {
|
||||
if (false !== strpos($procVersion, $name)) {
|
||||
return $distro;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of CLI environment
|
||||
*
|
||||
|
37
library/Icinga/Application/Version.php
Normal file
37
library/Icinga/Application/Version.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Application;
|
||||
|
||||
class Version
|
||||
{
|
||||
/**
|
||||
* Get the version of this instance of Icinga Web 2
|
||||
*
|
||||
* @return array|bool array on success, false otherwise
|
||||
*/
|
||||
public static function get()
|
||||
{
|
||||
if (false === ($appVersion = @file_get_contents(
|
||||
Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION'
|
||||
))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
if (false === ($res = preg_match(
|
||||
'/(?<!.)\s*(?P<gitCommitID>\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P<appVersion>.+?)\s*(?=[\),]).*?)?\))?\s*(?P<gitCommitDate>\S+)/ms',
|
||||
$appVersion,
|
||||
$matches
|
||||
)) || $res === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($matches as $key => $value) {
|
||||
if (is_int($key) || $value === '') {
|
||||
unset($matches[$key]);
|
||||
}
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ $baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
$baseDir = dirname($_SERVER['SCRIPT_FILENAME']);
|
||||
|
||||
// Fix aliases
|
||||
$remove = dirname($_SERVER['PHP_SELF']);
|
||||
$remove = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
|
||||
if (substr($ruri, 0, strlen($remove)) !== $remove) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4,17 +4,16 @@
|
||||
namespace Icinga\Authentication\User;
|
||||
|
||||
use DateTime;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\AuthenticationException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Repository\Repository;
|
||||
use Icinga\Repository\LdapRepository;
|
||||
use Icinga\Repository\RepositoryQuery;
|
||||
use Icinga\Protocol\Ldap\Exception as LdapException;
|
||||
use Icinga\Protocol\Ldap\Expression;
|
||||
use Icinga\User;
|
||||
|
||||
class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
class LdapUserBackend extends LdapRepository implements UserBackendInterface
|
||||
{
|
||||
/**
|
||||
* The base DN to use for a query
|
||||
@ -65,20 +64,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
)
|
||||
);
|
||||
|
||||
protected $groupOptions;
|
||||
|
||||
/**
|
||||
* Normed attribute names based on known LDAP environments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $normedAttributes = array(
|
||||
'uid' => 'uid',
|
||||
'user' => 'user',
|
||||
'inetorgperson' => 'inetOrgPerson',
|
||||
'samaccountname' => 'sAMAccountName'
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the base DN to use for a query
|
||||
*
|
||||
@ -179,34 +164,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
public function setGroupOptions(array $options)
|
||||
{
|
||||
$this->groupOptions = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGroupOptions()
|
||||
{
|
||||
return $this->groupOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given attribute name normed to known LDAP enviroments, if possible
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getNormedAttribute($name)
|
||||
{
|
||||
$loweredName = strtolower($name);
|
||||
if (array_key_exists($loweredName, $this->normedAttributes)) {
|
||||
return $this->normedAttributes[$loweredName];
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given configuration to this backend
|
||||
*
|
||||
@ -325,37 +282,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
|
||||
*
|
||||
* @param string|null $value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function retrieveGeneralizedTime($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (
|
||||
($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false
|
||||
) {
|
||||
return $dateTime->getTimeStamp();
|
||||
} else {
|
||||
Logger::debug(sprintf(
|
||||
'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) for user backend "%s".',
|
||||
$value,
|
||||
$this->getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given shadowExpire value defines that a user is permitted to login
|
||||
*
|
||||
@ -413,41 +339,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the user groups
|
||||
*
|
||||
* @TODO: Subject to change, see #7343
|
||||
*
|
||||
* @param string $dn
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups($dn)
|
||||
{
|
||||
if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = $this->ds->select()
|
||||
->setBase($this->groupOptions['group_base_dn'])
|
||||
->from(
|
||||
$this->groupOptions['group_class'],
|
||||
array($this->groupOptions['group_attribute'])
|
||||
)
|
||||
->where(
|
||||
$this->groupOptions['group_member_attribute'],
|
||||
$dn
|
||||
)
|
||||
->fetchAll();
|
||||
|
||||
$groups = array();
|
||||
foreach ($result as $group) {
|
||||
$groups[] = $group->{$this->groupOptions['group_attribute']};
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the given user
|
||||
*
|
||||
@ -472,15 +363,7 @@ class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$authenticated = $this->ds->testCredentials($userDn, $password);
|
||||
if ($authenticated) {
|
||||
$groups = $this->getGroups($userDn);
|
||||
if ($groups !== null) {
|
||||
$user->setGroups($groups);
|
||||
}
|
||||
}
|
||||
|
||||
return $authenticated;
|
||||
return $this->ds->testCredentials($userDn, $password);
|
||||
} catch (LdapException $e) {
|
||||
throw new AuthenticationException(
|
||||
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
namespace Icinga\Authentication\User;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Data\ConfigObject;
|
||||
@ -106,8 +107,17 @@ class UserBackend
|
||||
*
|
||||
* @throws ConfigurationError
|
||||
*/
|
||||
public static function create($name, ConfigObject $backendConfig)
|
||||
public static function create($name, ConfigObject $backendConfig = null)
|
||||
{
|
||||
if ($backendConfig === null) {
|
||||
$authConfig = Config::app('authentication');
|
||||
if ($authConfig->hasSection($name)) {
|
||||
$backendConfig = $authConfig->getSection($name);
|
||||
} else {
|
||||
throw new ConfigurationError('User backend "%s" does not exist', $name);
|
||||
}
|
||||
}
|
||||
|
||||
if ($backendConfig->name !== null) {
|
||||
$name = $backendConfig->name;
|
||||
}
|
||||
@ -165,12 +175,6 @@ class UserBackend
|
||||
$backend->setUserClass($backendConfig->get('user_class', 'user'));
|
||||
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName'));
|
||||
$backend->setFilter($backendConfig->filter);
|
||||
$backend->setGroupOptions(array(
|
||||
'group_base_dn' => $backendConfig->get('group_base_dn', $resource->getDN()),
|
||||
'group_attribute' => $backendConfig->get('group_attribute', 'sAMAccountName'),
|
||||
'group_member_attribute' => $backendConfig->get('group_member_attribute', 'member'),
|
||||
'group_class' => $backendConfig->get('group_class', 'group')
|
||||
));
|
||||
break;
|
||||
case 'ldap':
|
||||
$backend = new LdapUserBackend($resource);
|
||||
@ -178,12 +182,6 @@ class UserBackend
|
||||
$backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson'));
|
||||
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid'));
|
||||
$backend->setFilter($backendConfig->filter);
|
||||
$backend->setGroupOptions(array(
|
||||
'group_base_dn' => $backendConfig->group_base_dn,
|
||||
'group_attribute' => $backendConfig->group_attribute,
|
||||
'group_member_attribute' => $backendConfig->group_member_attribute,
|
||||
'group_class' => $backendConfig->group_class
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
|
627
library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
Normal file
627
library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
Normal file
@ -0,0 +1,627 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Authentication\UserGroup;
|
||||
|
||||
use Icinga\Authentication\User\UserBackend;
|
||||
use Icinga\Authentication\User\LdapUserBackend;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Protocol\Ldap\Expression;
|
||||
use Icinga\Repository\LdapRepository;
|
||||
use Icinga\Repository\RepositoryQuery;
|
||||
use Icinga\User;
|
||||
|
||||
class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface
|
||||
{
|
||||
/**
|
||||
* The base DN to use for a user query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userBaseDn;
|
||||
|
||||
/**
|
||||
* The base DN to use for a group query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $groupBaseDn;
|
||||
|
||||
/**
|
||||
* The objectClass where look for users
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userClass;
|
||||
|
||||
/**
|
||||
* The objectClass where look for groups
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $groupClass;
|
||||
|
||||
/**
|
||||
* The attribute name where to find a user's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userNameAttribute;
|
||||
|
||||
/**
|
||||
* The attribute name where to find a group's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $groupNameAttribute;
|
||||
|
||||
/**
|
||||
* The attribute name where to find a group's member
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $groupMemberAttribute;
|
||||
|
||||
/**
|
||||
* The custom LDAP filter to apply on a user query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userFilter;
|
||||
|
||||
/**
|
||||
* The custom LDAP filter to apply on a group query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $groupFilter;
|
||||
|
||||
/**
|
||||
* The columns which are not permitted to be queried
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filterColumns = array('group', 'user');
|
||||
|
||||
/**
|
||||
* The default sort rules to be applied on a query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sortRules = array(
|
||||
'group_name' => array(
|
||||
'order' => 'asc'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Normed attribute names based on known LDAP environments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $normedAttributes = array(
|
||||
'uid' => 'uid',
|
||||
'gid' => 'gid',
|
||||
'user' => 'user',
|
||||
'group' => 'group',
|
||||
'member' => 'member',
|
||||
'inetorgperson' => 'inetOrgPerson',
|
||||
'samaccountname' => 'sAMAccountName'
|
||||
);
|
||||
|
||||
/**
|
||||
* The name of this repository
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The datasource being used
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $ds;
|
||||
|
||||
/**
|
||||
* Create a new LDAP repository object
|
||||
*
|
||||
* @param Connection $ds The data source to use
|
||||
*/
|
||||
public function __construct($ds)
|
||||
{
|
||||
$this->ds = $ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given attribute name normed to known LDAP enviroments, if possible
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getNormedAttribute($name)
|
||||
{
|
||||
$loweredName = strtolower($name);
|
||||
if (array_key_exists($loweredName, $this->normedAttributes)) {
|
||||
return $this->normedAttributes[$loweredName];
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this repository's name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this repository's name
|
||||
*
|
||||
* In case no name has been explicitly set yet, the class name is returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base DN to use for a user query
|
||||
*
|
||||
* @param string $baseDn
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserBaseDn($baseDn)
|
||||
{
|
||||
if (($baseDn = trim($baseDn))) {
|
||||
$this->userBaseDn = $baseDn;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base DN to use for a user query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserBaseDn()
|
||||
{
|
||||
return $this->userBaseDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base DN to use for a group query
|
||||
*
|
||||
* @param string $baseDn
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroupBaseDn($baseDn)
|
||||
{
|
||||
if (($baseDn = trim($baseDn))) {
|
||||
$this->groupBaseDn = $baseDn;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base DN to use for a group query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupBaseDn()
|
||||
{
|
||||
return $this->groupBaseDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the objectClass where to look for users
|
||||
*
|
||||
* @param string $userClass
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserClass($userClass)
|
||||
{
|
||||
$this->userClass = $this->getNormedAttribute($userClass);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objectClass where to look for users
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserClass()
|
||||
{
|
||||
return $this->userClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the objectClass where to look for groups
|
||||
*
|
||||
* Sets also the base table name for the underlying repository.
|
||||
*
|
||||
* @param string $groupClass
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroupClass($groupClass)
|
||||
{
|
||||
$this->baseTable = $this->groupClass = $this->getNormedAttribute($groupClass);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objectClass where to look for groups
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupClass()
|
||||
{
|
||||
return $this->groupClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute name where to find a user's name
|
||||
*
|
||||
* @param string $userNameAttribute
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserNameAttribute($userNameAttribute)
|
||||
{
|
||||
$this->userNameAttribute = $this->getNormedAttribute($userNameAttribute);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attribute name where to find a user's name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserNameAttribute()
|
||||
{
|
||||
return $this->userNameAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute name where to find a group's name
|
||||
*
|
||||
* @param string $groupNameAttribute
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroupNameAttribute($groupNameAttribute)
|
||||
{
|
||||
$this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attribute name where to find a group's name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupNameAttribute()
|
||||
{
|
||||
return $this->groupNameAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute name where to find a group's member
|
||||
*
|
||||
* @param string $groupMemberAttribute
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroupMemberAttribute($groupMemberAttribute)
|
||||
{
|
||||
$this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attribute name where to find a group's member
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupMemberAttribute()
|
||||
{
|
||||
return $this->groupMemberAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom LDAP filter to apply on a user query
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserFilter($filter)
|
||||
{
|
||||
if (($filter = trim($filter))) {
|
||||
$this->userFilter = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom LDAP filter to apply on a user query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserFilter()
|
||||
{
|
||||
return $this->userFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom LDAP filter to apply on a group query
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroupFilter($filter)
|
||||
{
|
||||
if (($filter = trim($filter))) {
|
||||
$this->groupFilter = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom LDAP filter to apply on a group query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupFilter()
|
||||
{
|
||||
return $this->groupFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new query for the given columns
|
||||
*
|
||||
* @param array $columns The desired columns, if null all columns will be queried
|
||||
*
|
||||
* @return RepositoryQuery
|
||||
*/
|
||||
public function select(array $columns = null)
|
||||
{
|
||||
$query = parent::select($columns);
|
||||
$query->getQuery()->setBase($this->groupBaseDn);
|
||||
if ($this->groupFilter) {
|
||||
// TODO(jom): This should differentiate between groups and their memberships
|
||||
$query->getQuery()->where(new Expression($this->groupFilter));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this repository's query columns
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet
|
||||
*/
|
||||
protected function initializeQueryColumns()
|
||||
{
|
||||
if ($this->groupClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
|
||||
}
|
||||
if ($this->groupNameAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first');
|
||||
}
|
||||
|
||||
if ($this->ds->getCapabilities()->hasAdOid()) {
|
||||
$createdAtAttribute = 'whenCreated';
|
||||
$lastModifiedAttribute = 'whenChanged';
|
||||
} else {
|
||||
$createdAtAttribute = 'createTimestamp';
|
||||
$lastModifiedAttribute = 'modifyTimestamp';
|
||||
}
|
||||
|
||||
// TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality!
|
||||
$columns = array(
|
||||
'group' => $this->groupNameAttribute,
|
||||
'group_name' => $this->groupNameAttribute,
|
||||
'user' => $this->groupMemberAttribute,
|
||||
'user_name' => $this->groupMemberAttribute,
|
||||
'created_at' => $createdAtAttribute,
|
||||
'last_modified' => $lastModifiedAttribute
|
||||
);
|
||||
return array('group' => $columns, 'group_membership' => $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this repository's conversion rules
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case $this->groupClass has not been set yet
|
||||
*/
|
||||
protected function initializeConversionRules()
|
||||
{
|
||||
if ($this->groupClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->groupClass => array(
|
||||
'created_at' => 'generalized_time',
|
||||
'last_modified' => 'generalized_time'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the requested table exists
|
||||
*
|
||||
* This will return $this->groupClass in case $table equals "group" or "group_membership".
|
||||
*
|
||||
* @param string $table The table to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context
|
||||
* (unused by the base implementation)
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ProgrammingError In case the given table does not exist
|
||||
*/
|
||||
public function requireTable($table, RepositoryQuery $query = null)
|
||||
{
|
||||
$table = parent::requireTable($table, $query);
|
||||
if ($table === 'group' || $table === 'group_membership') {
|
||||
$table = $this->groupClass;
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the groups the given user is a member of
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMemberships(User $user)
|
||||
{
|
||||
$userQuery = $this->ds
|
||||
->select()
|
||||
->from($this->userClass)
|
||||
->where($this->userNameAttribute, $user->getUsername())
|
||||
->setBase($this->userBaseDn)
|
||||
->setUsePagedResults(false);
|
||||
if ($this->userFilter) {
|
||||
$userQuery->where(new Expression($this->userFilter));
|
||||
}
|
||||
|
||||
if (($userDn = $userQuery->fetchDn()) === null) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$groupQuery = $this->ds
|
||||
->select()
|
||||
->from($this->groupClass, array($this->groupNameAttribute))
|
||||
->where($this->groupMemberAttribute, $userDn)
|
||||
->setBase($this->groupBaseDn);
|
||||
if ($this->groupFilter) {
|
||||
$groupQuery->where(new Expression($this->groupFilter));
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
foreach ($groupQuery as $row) {
|
||||
$groups[] = $row->{$this->groupNameAttribute};
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given configuration on this backend
|
||||
*
|
||||
* @param ConfigObject $config
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws ConfigurationError In case a linked user backend does not exist or is invalid
|
||||
*/
|
||||
public function setConfig(ConfigObject $config)
|
||||
{
|
||||
if ($config->backend === 'ldap') {
|
||||
$defaults = $this->getOpenLdapDefaults();
|
||||
} elseif ($config->backend === 'msldap') {
|
||||
$defaults = $this->getActiveDirectoryDefaults();
|
||||
} else {
|
||||
$defaults = new ConfigObject();
|
||||
}
|
||||
|
||||
if ($config->user_backend && $config->user_backend !== 'none') {
|
||||
$userBackend = UserBackend::create($config->user_backend);
|
||||
if (! $userBackend instanceof LdapUserBackend) {
|
||||
throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend);
|
||||
}
|
||||
|
||||
if (
|
||||
$this->ds->getHostname() !== $userBackend->getDataSource()->getHostname()
|
||||
|| $this->ds->getPort() !== $userBackend->getDataSource()->getPort()
|
||||
) {
|
||||
// TODO(jom): Elaborate whether it makes sense to link directories on different hosts
|
||||
throw new ConfigurationError(
|
||||
'It is required that a linked user backend refers to the '
|
||||
. 'same directory as it\'s user group backend counterpart'
|
||||
);
|
||||
}
|
||||
|
||||
$defaults->merge(array(
|
||||
'user_base_dn' => $userBackend->getBaseDn(),
|
||||
'user_class' => $userBackend->getUserClass(),
|
||||
'user_name_attribute' => $userBackend->getUserNameAttribute(),
|
||||
'user_filter' => $userBackend->getFilter()
|
||||
));
|
||||
}
|
||||
|
||||
return $this
|
||||
->setGroupBaseDn($config->base_dn)
|
||||
->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn()))
|
||||
->setGroupClass($config->get('group_class', $defaults->group_class))
|
||||
->setUserClass($config->get('user_class', $defaults->user_class))
|
||||
->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
|
||||
->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute))
|
||||
->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute))
|
||||
->setGroupFilter($config->filter)
|
||||
->setUserFilter($config->user_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration defaults for an OpenLDAP environment
|
||||
*
|
||||
* @return ConfigObject
|
||||
*/
|
||||
public function getOpenLdapDefaults()
|
||||
{
|
||||
return new ConfigObject(array(
|
||||
'group_class' => 'group',
|
||||
'user_class' => 'inetOrgPerson',
|
||||
'group_name_attribute' => 'gid',
|
||||
'user_name_attribute' => 'uid',
|
||||
'group_member_attribute' => 'member'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration defaults for an ActiveDirectory environment
|
||||
*
|
||||
* @return ConfigObject
|
||||
*/
|
||||
public function getActiveDirectoryDefaults()
|
||||
{
|
||||
return new ConfigObject(array(
|
||||
'group_class' => 'group',
|
||||
'user_class' => 'user',
|
||||
'group_name_attribute' => 'sAMAccountName',
|
||||
'user_name_attribute' => 'sAMAccountName',
|
||||
'group_member_attribute' => 'member'
|
||||
));
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ class UserGroupBackend
|
||||
*/
|
||||
protected static $defaultBackends = array(
|
||||
'db',
|
||||
'ldap',
|
||||
'msldap',
|
||||
//'ini'
|
||||
);
|
||||
|
||||
@ -156,6 +158,11 @@ class UserGroupBackend
|
||||
case 'ini':
|
||||
$backend = new IniUserGroupBackend($resource);
|
||||
break;
|
||||
case 'ldap':
|
||||
case 'msldap':
|
||||
$backend = new LdapUserGroupBackend($resource);
|
||||
$backend->setConfig($backendConfig);
|
||||
break;
|
||||
}
|
||||
|
||||
$backend->setName($name);
|
||||
|
@ -47,7 +47,12 @@ class Params
|
||||
$noOptionFlag = true;
|
||||
} elseif (!$noOptionFlag && substr($argv[$i], 0, 2) === '--') {
|
||||
$key = substr($argv[$i], 2);
|
||||
if (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') {
|
||||
$matches = array();
|
||||
if (1 === preg_match(
|
||||
'/(?<!.)([^=]+)=(.*)(?!.)/ms', $key, $matches
|
||||
)) {
|
||||
$this->params[$matches[1]] = $matches[2];
|
||||
} elseif (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') {
|
||||
$this->params[$key] = true;
|
||||
} elseif (array_key_exists($key, $this->params)) {
|
||||
if (!is_array($this->params[$key])) {
|
||||
|
@ -313,14 +313,19 @@ class IniEditor
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
$this->cleanUpWhitespaces();
|
||||
return rtrim(implode(PHP_EOL, $this->text)) . PHP_EOL;
|
||||
$this->normalizeSectionSpacing();
|
||||
|
||||
// trim leading and trailing whitespaces from generated file
|
||||
$txt = trim(implode(PHP_EOL, $this->text)) . PHP_EOL;
|
||||
|
||||
// replace linebreaks, unless they preceed a comment or a section
|
||||
return preg_replace("/\n[\n]*([^;\[])/", "\n$1", $txt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all unneeded line breaks between sections
|
||||
* normalize section spacing according to the current settings
|
||||
*/
|
||||
private function cleanUpWhitespaces()
|
||||
private function normalizeSectionSpacing()
|
||||
{
|
||||
$i = count($this->text) - 1;
|
||||
for (; $i > 0; $i--) {
|
||||
@ -328,24 +333,18 @@ class IniEditor
|
||||
if ($this->isSectionDeclaration($line) && $i > 0) {
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
/*
|
||||
* Ignore comments that are glued to the section declaration
|
||||
*/
|
||||
// ignore comments that are glued to the section declaration
|
||||
while ($i > 0 && $this->isComment($line)) {
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
}
|
||||
/*
|
||||
* Remove whitespaces between the sections
|
||||
*/
|
||||
// remove whitespaces between the sections
|
||||
while ($i > 0 && preg_match('/^\s*$/', $line) === 1) {
|
||||
$this->deleteLine($i);
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
}
|
||||
/*
|
||||
* Refresh section separators
|
||||
*/
|
||||
// refresh section separators
|
||||
if ($i !== 0 && $this->sectionSeparators > 0) {
|
||||
$this->insertAtLine($i + 1, str_repeat(PHP_EOL, $this->sectionSeparators - 1));
|
||||
}
|
||||
@ -621,6 +620,6 @@ class IniEditor
|
||||
|
||||
private function sanitize($value)
|
||||
{
|
||||
return str_replace('\n', '', $value);
|
||||
return str_replace("\n", '', $value);
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
|
||||
{
|
||||
if (file_exists($this->_filename)) {
|
||||
$oldconfig = new Zend_Config_Ini($this->_filename);
|
||||
$content = file_get_contents($this->_filename);
|
||||
$content = trim(file_get_contents($this->_filename));
|
||||
} else {
|
||||
$oldconfig = new Zend_Config(array());
|
||||
$content = '';
|
||||
|
66
library/Icinga/Repository/LdapRepository.php
Normal file
66
library/Icinga/Repository/LdapRepository.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Repository;
|
||||
|
||||
use Icinga\Protocol\Ldap\Connection;
|
||||
|
||||
/**
|
||||
* Abstract base class for concrete LDAP repository implementations
|
||||
*
|
||||
* Additionally provided features:
|
||||
* <ul>
|
||||
* <li>Attribute name normalization</li>
|
||||
* </ul>
|
||||
*/
|
||||
abstract class LdapRepository extends Repository
|
||||
{
|
||||
/**
|
||||
* The datasource being used
|
||||
*
|
||||
* @var Connection
|
||||
*/
|
||||
protected $ds;
|
||||
|
||||
/**
|
||||
* Normed attribute names based on known LDAP environments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $normedAttributes = array(
|
||||
'uid' => 'uid',
|
||||
'gid' => 'gid',
|
||||
'user' => 'user',
|
||||
'group' => 'group',
|
||||
'member' => 'member',
|
||||
'inetorgperson' => 'inetOrgPerson',
|
||||
'samaccountname' => 'sAMAccountName'
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new LDAP repository object
|
||||
*
|
||||
* @param Connection $ds The data source to use
|
||||
*/
|
||||
public function __construct(Connection $ds)
|
||||
{
|
||||
parent::__construct($ds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given attribute name normed to known LDAP enviroments, if possible
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getNormedAttribute($name)
|
||||
{
|
||||
$loweredName = strtolower($name);
|
||||
if (array_key_exists($loweredName, $this->normedAttributes)) {
|
||||
return $this->normedAttributes[$loweredName];
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
@ -599,6 +599,37 @@ abstract class Repository implements Selectable
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
|
||||
*
|
||||
* @param string|null $value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function retrieveGeneralizedTime($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (
|
||||
($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false
|
||||
|| ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false
|
||||
) {
|
||||
return $dateTime->getTimeStamp();
|
||||
} else {
|
||||
Logger::debug(sprintf(
|
||||
'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) in repository "%s".',
|
||||
$value,
|
||||
$this->getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the requested table exists
|
||||
*
|
||||
|
@ -63,7 +63,7 @@ class File extends SplFileObject
|
||||
throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath));
|
||||
}
|
||||
|
||||
$file = new static($path, 'x');
|
||||
$file = new static($path, 'x+');
|
||||
|
||||
if (! @chmod($path, $accessMode)) {
|
||||
$error = error_get_last();
|
||||
|
@ -100,7 +100,11 @@ class Translator
|
||||
{
|
||||
$contextString = "{$context}\004{$text}";
|
||||
|
||||
$translation = dcgettext($domain, $contextString, LC_MESSAGES);
|
||||
$translation = dcgettext(
|
||||
$domain,
|
||||
$contextString,
|
||||
defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL
|
||||
);
|
||||
|
||||
if ($translation == $contextString) {
|
||||
return $text;
|
||||
@ -126,7 +130,13 @@ class Translator
|
||||
{
|
||||
$contextString = "{$context}\004{$textSingular}";
|
||||
|
||||
$translation = dcngettext($domain, $contextString, $textPlural, $number, LC_MESSAGES);
|
||||
$translation = dcngettext(
|
||||
$domain,
|
||||
$contextString,
|
||||
$textPlural,
|
||||
$number,
|
||||
defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL
|
||||
);
|
||||
|
||||
if ($translation == $contextString || $translation == $textPlural) {
|
||||
return ($number == 1 ? $textSingular : $textPlural);
|
||||
|
@ -855,8 +855,19 @@ class Form extends Zend_Form
|
||||
public function populate(array $defaults)
|
||||
{
|
||||
$this->create($defaults);
|
||||
$this->preserveDefaults($this, $defaults);
|
||||
return parent::populate($defaults);
|
||||
}
|
||||
|
||||
foreach ($this->getElements() as $name => $_) {
|
||||
/**
|
||||
* Recurse the given form and unset all unchanged default values
|
||||
*
|
||||
* @param Zend_Form $form
|
||||
* @param array $defaults
|
||||
*/
|
||||
protected function preserveDefaults(Zend_Form $form, array & $defaults)
|
||||
{
|
||||
foreach ($form->getElements() as $name => $_) {
|
||||
if (
|
||||
array_key_exists($name, $defaults)
|
||||
&& array_key_exists($name . static::DEFAULT_SUFFIX, $defaults)
|
||||
@ -866,7 +877,9 @@ class Form extends Zend_Form
|
||||
}
|
||||
}
|
||||
|
||||
return parent::populate($defaults);
|
||||
foreach ($form->getSubForms() as $_ => $subForm) {
|
||||
$this->preserveDefaults($subForm, $defaults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,6 +279,11 @@ class Menu implements RecursiveIterator
|
||||
'priority' => 990,
|
||||
'renderer' => 'ForeignMenuItemRenderer'
|
||||
));
|
||||
|
||||
$this->add(t('About'), array(
|
||||
'url' => 'about',
|
||||
'priority' => 1000
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,12 @@ $this->addHelperFunction('url', function ($path = null, $params = null) {
|
||||
} else {
|
||||
$url = Url::fromPath($path);
|
||||
}
|
||||
|
||||
if ($params !== null) {
|
||||
if ($url === $path) {
|
||||
$url = clone $url;
|
||||
}
|
||||
|
||||
$url->overwriteParams($params);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="content">
|
||||
<?php foreach (/** @var \Icinga\Module\Doc\Renderer\DocSearchRenderer[] $searches */ $searches as $title => $search): ?>
|
||||
<?php if (! $search->isEmpty()): ?>
|
||||
<h1><?= $this->escape($title) ?></h1>
|
||||
<?= $search ?>
|
||||
<?php endif ?>
|
||||
<h2><?= $this->escape($title) ?></h2>
|
||||
<?= $search->isEmpty()
|
||||
? $this->translate('No documentation found matching the filter')
|
||||
: $search ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
|
65
modules/doc/doc/security.md
Normal file
65
modules/doc/doc/security.md
Normal file
@ -0,0 +1,65 @@
|
||||
# <a id="monitoring-security"></a> Security
|
||||
|
||||
The monitoring module provides an additional set of restrictions and permissions
|
||||
that can be used for access control. The following sections will list those
|
||||
restrictions and permissions in detail:
|
||||
|
||||
|
||||
## Permissions
|
||||
|
||||
The Icinga Web 2 monitoring module can send commands to the current Icinga2 instance
|
||||
through the command pipe. A user needs specific permissions to be able to send those
|
||||
commands when using the monitoring module.
|
||||
|
||||
|
||||
| Name | Permits |
|
||||
|---------------------------------------------|-----------------------------------------------------------------------------|
|
||||
| monitoring/command/* | Allow all commands |
|
||||
| monitoring/command/schedule-check | Allow scheduling host and service checks' |
|
||||
| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems |
|
||||
| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements |
|
||||
| monitoring/command/comment/* | Allow adding and deleting host and service comments |
|
||||
| monitoring/command/comment/add | Allow commenting on hosts and services |
|
||||
| monitoring/command/downtime/delete | Allow deleting host and service downtimes' |
|
||||
| monitoring/command/process-check-result | Allow processing host and service check results |
|
||||
| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis |
|
||||
| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects |
|
||||
| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services |
|
||||
|
||||
|
||||
## <a id="monitoring-security-restrictions"></a> Restrictions
|
||||
|
||||
The monitoring module allows filtering objects:
|
||||
|
||||
|
||||
| Keys | Restricts |
|
||||
|----------------------------|-----------------------------------------------|
|
||||
| monitoring/filter/objects | Applies a filter to all hosts and services |
|
||||
|
||||
|
||||
This filter will affect all hosts and services. Furthermore, it will also
|
||||
affect all related objects, like notifications, downtimes or events. If a
|
||||
service is hidden, all notifications, downtimes on that service will be hidden too.
|
||||
|
||||
|
||||
### Filter Column Names
|
||||
|
||||
The following filter column names are available in filter expressions:
|
||||
|
||||
|
||||
| Column |
|
||||
|------------------------------------------------------|
|
||||
| host |
|
||||
| host_alias |
|
||||
| host_display_name |
|
||||
| host_name |
|
||||
| hostgroup |
|
||||
| hostgroup_alias |
|
||||
| hostgroup_name |
|
||||
| service |
|
||||
| service_description |
|
||||
| service_display_name |
|
||||
| service_group |
|
||||
| service_group_alias |
|
||||
| service_group_name |
|
||||
| + all custom variables prefixed with host or service |
|
@ -1,4 +1,4 @@
|
||||
Module: doc
|
||||
Version: 2.0.0
|
||||
Version: 2.0.0-rc1
|
||||
Description: Documentation module
|
||||
Extracts, shows and exports documentation for Icinga Web 2 and its modules.
|
||||
|
@ -20,7 +20,7 @@ class ConferenceCommand extends Command
|
||||
* Use this command in case you feel that you should be friendly. Should
|
||||
* be executed as follows:
|
||||
*
|
||||
* icingacli monitoring conference welcome --watch 1
|
||||
* icingacli monitoring conference welcome --watch=1
|
||||
*/
|
||||
public function welcomeAction()
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ namespace Icinga\Module\Monitoring\Clicommands;
|
||||
|
||||
use Icinga\Module\Monitoring\Backend;
|
||||
use Icinga\Module\Monitoring\Cli\CliUtils;
|
||||
use Icinga\Util\Format;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Cli\Command;
|
||||
use Icinga\File\Csv;
|
||||
use Icinga\Module\Monitoring\Plugin\PerfdataSet;
|
||||
@ -124,19 +124,19 @@ class ListCommand extends Command
|
||||
* --verbose Show detailled output
|
||||
* --showsql Dump generated SQL query (DB backend only)
|
||||
*
|
||||
* --format <csv|json|<custom>>
|
||||
* --format=<csv|json|<custom>>
|
||||
* Dump columns in the given format. <custom> format allows $column$
|
||||
* placeholders, e.g. --format '$host$: $service$'
|
||||
* placeholders, e.g. --format='$host$: $service$'
|
||||
*
|
||||
* --<column> [filter]
|
||||
* --<column>[=filter]
|
||||
* Filter given column by optional filter. Boolean (1/0) columns are
|
||||
* true if no filter value is given.
|
||||
*
|
||||
* EXAMPLES
|
||||
*
|
||||
* icingacli monitoring list --unhandled
|
||||
* icingacli monitoring list --host local* --service *disk*
|
||||
* icingacli monitoring list --format '$host$: $service$'
|
||||
* icingacli monitoring list --host=local* --service=*disk*
|
||||
* icingacli monitoring list --format='$host$: $service$'
|
||||
*/
|
||||
public function statusAction()
|
||||
{
|
||||
@ -299,7 +299,7 @@ class ListCommand extends Command
|
||||
$leaf,
|
||||
$screen->underline($row->service_description),
|
||||
$screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'),
|
||||
ucfirst(Format::timeSince($row->service_last_state_change))
|
||||
ucfirst(DateFormatter::timeSince($row->service_last_state_change))
|
||||
);
|
||||
if ($this->isVerbose) {
|
||||
$out .= $emptyLine . preg_replace(
|
||||
|
@ -27,8 +27,8 @@ class NrpeCommand extends Command
|
||||
*
|
||||
* EXAMPLE
|
||||
*
|
||||
* icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn 80% \
|
||||
* --MaxCrit 90% --type physical
|
||||
* icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn=80% \
|
||||
* --MaxCrit=90% --type=physical
|
||||
*/
|
||||
public function checkAction()
|
||||
{
|
||||
|
@ -133,6 +133,8 @@ class Monitoring_HostsController extends Controller
|
||||
$this->view->objects = $this->hostList;
|
||||
$this->view->unhandledObjects = $this->hostList->getUnhandledObjects();
|
||||
$this->view->problemObjects = $this->hostList->getProblemObjects();
|
||||
$this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
|
||||
->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
|
||||
$this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')
|
||||
->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
|
||||
$this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime')
|
||||
|
@ -78,20 +78,6 @@ class Monitoring_ShowController extends Controller
|
||||
$this->setupPaginationControl($this->view->history, 50);
|
||||
}
|
||||
|
||||
public function servicesAction()
|
||||
{
|
||||
$this->setAutorefreshInterval(15);
|
||||
$this->getTabs()->activate('services');
|
||||
$this->_setParam('service', '');
|
||||
// TODO: This used to be a hack and still is. Modifying query string here.
|
||||
$_SERVER['QUERY_STRING'] = (string) $this->params->without('service')->set('limit', '');
|
||||
$this->view->services = $this->view->action('services', 'list', 'monitoring', array(
|
||||
'view' => 'compact',
|
||||
'sort' => 'service_description',
|
||||
));
|
||||
$this->fetchHostStats();
|
||||
}
|
||||
|
||||
protected function fetchHostStats()
|
||||
{
|
||||
$this->view->stats = $this->backend->select()->from('statusSummary', array(
|
||||
@ -228,19 +214,6 @@ class Monitoring_ShowController extends Controller
|
||||
)
|
||||
);
|
||||
}
|
||||
$tabs->add(
|
||||
'services',
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translate('List all services on host %s'),
|
||||
$isService ? $object->getHost()->getName() : $object->getName()
|
||||
),
|
||||
'label' => $this->translate('Services'),
|
||||
'icon' => 'services',
|
||||
'url' => 'monitoring/show/services',
|
||||
'urlParams' => $params,
|
||||
)
|
||||
);
|
||||
if ($this->backend->hasQuery('eventHistory')) {
|
||||
$tabs->add(
|
||||
'history',
|
||||
|
@ -3,10 +3,19 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Forms\Config\Instance;
|
||||
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Web\Form;
|
||||
|
||||
class RemoteInstanceForm extends Form
|
||||
{
|
||||
/**
|
||||
* The available monitoring instance resources split by type
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $resources;
|
||||
|
||||
/**
|
||||
* (non-PHPDoc)
|
||||
* @see Form::init() For the method documentation.
|
||||
@ -16,12 +25,89 @@ class RemoteInstanceForm extends Form
|
||||
$this->setName('form_config_monitoring_instance_remote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all available ssh identity resources
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
public function loadResources()
|
||||
{
|
||||
$resourceConfig = ResourceFactory::getResourceConfigs();
|
||||
|
||||
$resources = array();
|
||||
foreach ($resourceConfig as $name => $resource) {
|
||||
if ($resource->type === 'ssh') {
|
||||
$resources['ssh'][$name] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($resources)) {
|
||||
throw new ConfigurationError($this->translate('Could not find any valid monitoring instance resources'));
|
||||
}
|
||||
|
||||
$this->resources = $resources;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPDoc)
|
||||
* @see Form::createElements() For the method documentation.
|
||||
*/
|
||||
public function createElements(array $formData = array())
|
||||
{
|
||||
$useResource = isset($formData['use_resource']) ? $formData['use_resource'] : $this->getValue('use_resource');
|
||||
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'use_resource',
|
||||
array(
|
||||
'label' => $this->translate('Use SSH Identity'),
|
||||
'description' => $this->translate('Make use of the ssh identity resource'),
|
||||
'autosubmit' => true,
|
||||
'ignore' => true
|
||||
)
|
||||
);
|
||||
|
||||
if ($useResource) {
|
||||
|
||||
$this->loadResources();
|
||||
|
||||
$decorators = static::$defaultElementDecorators;
|
||||
array_pop($decorators); // Removes the HtmlTag decorator
|
||||
|
||||
$this->addElement(
|
||||
'select',
|
||||
'resource',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('SSH Identity'),
|
||||
'description' => $this->translate('The resource to use'),
|
||||
'decorators' => $decorators,
|
||||
'multiOptions' => $this->resources['ssh'],
|
||||
'value' => current($this->resources['ssh']),
|
||||
'autosubmit' => false
|
||||
)
|
||||
);
|
||||
$resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
|
||||
$this->addElement(
|
||||
'note',
|
||||
'resource_note',
|
||||
array(
|
||||
'escape' => false,
|
||||
'decorators' => $decorators,
|
||||
'value' => sprintf(
|
||||
'<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
|
||||
$this->getView()->url('config/editresource', array('resource' => $resourceName)),
|
||||
sprintf($this->translate('Show the configuration of the %s resource'), $resourceName),
|
||||
$this->translate('Show resource configuration')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addElements(array(
|
||||
array(
|
||||
'text',
|
||||
@ -43,8 +129,11 @@ class RemoteInstanceForm extends Form
|
||||
'description' => $this->translate('SSH port to connect to on the remote Icinga instance'),
|
||||
'value' => 22
|
||||
)
|
||||
),
|
||||
array(
|
||||
)
|
||||
));
|
||||
|
||||
if (! $useResource) {
|
||||
$this->addElement(
|
||||
'text',
|
||||
'user',
|
||||
array(
|
||||
@ -55,18 +144,20 @@ class RemoteInstanceForm extends Form
|
||||
. ' possible for this user'
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$this->addElement(
|
||||
'text',
|
||||
'path',
|
||||
array(
|
||||
'text',
|
||||
'path',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('Command File'),
|
||||
'value' => '/var/run/icinga2/cmd/icinga2.cmd',
|
||||
'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance')
|
||||
)
|
||||
'required' => true,
|
||||
'label' => $this->translate('Command File'),
|
||||
'value' => '/var/run/icinga2/cmd/icinga2.cmd',
|
||||
'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance')
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +143,11 @@ class InstanceConfigForm extends ConfigForm
|
||||
|
||||
$instanceConfig = $this->config->getSection($instanceName)->toArray();
|
||||
$instanceConfig['name'] = $instanceName;
|
||||
|
||||
if (isset($instanceConfig['resource'])) {
|
||||
$instanceConfig['use_resource'] = true;
|
||||
}
|
||||
|
||||
$this->populate($instanceConfig);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ if (count($hosts) === 0) {
|
||||
$this->translatePlural('%u unhandled service', '%u unhandled services', $host->host_unhandled_services),
|
||||
$host->host_unhandled_services
|
||||
),
|
||||
'monitoring/show/services',
|
||||
'monitoring/list/services',
|
||||
array(
|
||||
'host' => $host->host_name,
|
||||
'service_problem' => 1,
|
||||
|
@ -67,7 +67,7 @@ foreach ($serviceDescriptions as $service_description): ?>
|
||||
<th>
|
||||
<?= $this->qlink(
|
||||
$host_name,
|
||||
'monitoring/show/services?' . $serviceFilter,
|
||||
'monitoring/list/services?' . $serviceFilter,
|
||||
array('host' => $host_name),
|
||||
array('title' => sprintf($this->translate('List all reported services on host %s'), $host_name))
|
||||
); ?>
|
||||
|
@ -12,8 +12,7 @@ function urlAddFilterOptional($url, $filter, $optional) {
|
||||
return $url->setQueryString($f->toQueryString());
|
||||
}
|
||||
|
||||
$selfUrl = Url::fromPath('monitoring/show/services', array('host' => $object->host_name));
|
||||
$currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
|
||||
$selfUrl = Url::fromPath('monitoring/list/services', array('host' => $object->host_name));
|
||||
?><div class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : ''; ?>>
|
||||
<?php if ($object->stats->services_total): ?>
|
||||
<?= $this->qlink(
|
||||
@ -27,15 +26,18 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
|
||||
),
|
||||
$selfUrl,
|
||||
null,
|
||||
array('title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List all %u service on host %s',
|
||||
'List all %u services on host %s',
|
||||
$object->stats->services_total
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List all %u service on host %s',
|
||||
'List all %u services on host %s',
|
||||
$object->stats->services_total
|
||||
),
|
||||
$object->stats->services_total,
|
||||
$object->host_name
|
||||
),
|
||||
$object->stats->services_total,
|
||||
$object->host_name
|
||||
))
|
||||
'data-base-target' => '_next'
|
||||
)
|
||||
); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->translate('No services configured on this host'); ?>
|
||||
@ -43,20 +45,23 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
|
||||
|
||||
<span class="badges">
|
||||
<?php if ($object->stats->services_ok): ?>
|
||||
<span class="state ok<?= $currentUrl === $selfUrl->with('service_state', 0)->getRelativeUrl() ? ' active' : ''; ?>">
|
||||
<span class="state ok">
|
||||
<?= $this->qlink(
|
||||
$object->stats->services_ok,
|
||||
$selfUrl,
|
||||
array('service_state' => 0),
|
||||
array('title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state OK on host %s',
|
||||
'List %u services which are currently in state OK on host %s',
|
||||
$object->stats->services_ok
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state OK on host %s',
|
||||
'List %u services which are currently in state OK on host %s',
|
||||
$object->stats->services_ok
|
||||
),
|
||||
$object->stats->services_ok,
|
||||
$object->host_name
|
||||
),
|
||||
$object->stats->services_ok,
|
||||
$object->host_name
|
||||
))
|
||||
'data-base-target' => '_next'
|
||||
)
|
||||
); ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
@ -68,61 +73,49 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $
|
||||
$unhandled = $pre . '_unhandled';
|
||||
$paramsHandled = array('service_state' => $stateId, 'service_handled' => 1);
|
||||
$paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0);
|
||||
if ($object->stats->$unhandled) {
|
||||
$compareUrl = $selfUrl->with($paramsUnhandled)->getRelativeUrl();
|
||||
} else {
|
||||
$compareUrl = $selfUrl->with($paramsHandled)->getRelativeUrl();
|
||||
}
|
||||
|
||||
if ($compareUrl === $currentUrl) {
|
||||
$active = ' active';
|
||||
} else {
|
||||
$active = '';
|
||||
}
|
||||
|
||||
echo '<span class="state ' . $state . $active . ($object->stats->$unhandled ? '' : ' handled') . '">';
|
||||
echo '<span class="state ' . $state . ($object->stats->$unhandled ? '' : ' handled') . '">';
|
||||
if ($object->stats->$unhandled) {
|
||||
|
||||
echo $this->qlink(
|
||||
$object->stats->$unhandled,
|
||||
$selfUrl,
|
||||
$paramsUnhandled,
|
||||
array('title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state %s on host %s',
|
||||
'List %u services which are currently in state %s on host %s',
|
||||
$object->stats->$unhandled
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state %s on host %s',
|
||||
'List %u services which are currently in state %s on host %s',
|
||||
$object->stats->$unhandled
|
||||
),
|
||||
$object->stats->$unhandled,
|
||||
Service::getStateText($stateId, true),
|
||||
$object->host_name
|
||||
),
|
||||
$object->stats->$unhandled,
|
||||
Service::getStateText($stateId, true),
|
||||
$object->host_name
|
||||
))
|
||||
'data-base-target' => '_next'
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($object->stats->$handled) {
|
||||
|
||||
if ($selfUrl->with($paramsHandled)->getRelativeUrl() === $currentUrl) {
|
||||
$active = ' active';
|
||||
} else {
|
||||
$active = '';
|
||||
}
|
||||
if ($object->stats->$unhandled) {
|
||||
echo '<span class="state handled ' . $state . $active . '">';
|
||||
echo '<span class="state handled ' . $state . '">';
|
||||
}
|
||||
echo $this->qlink(
|
||||
$object->stats->$handled,
|
||||
$selfUrl,
|
||||
$paramsHandled,
|
||||
array('title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state %s (Acknowledged) on host %s',
|
||||
'List %u services which are currently in state %s (Acknowledged) on host %s',
|
||||
$object->stats->$handled
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state %s (Acknowledged) on host %s',
|
||||
'List %u services which are currently in state %s (Acknowledged) on host %s',
|
||||
$object->stats->$handled
|
||||
),
|
||||
$object->stats->$handled,
|
||||
Service::getStateText($stateId, true),
|
||||
$object->host_name
|
||||
),
|
||||
$object->stats->$handled,
|
||||
Service::getStateText($stateId, true),
|
||||
$object->host_name
|
||||
))
|
||||
'data-base-target' => '_next'
|
||||
)
|
||||
);
|
||||
if ($object->stats->$unhandled) {
|
||||
echo "</span>\n";
|
||||
@ -133,22 +126,25 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $
|
||||
}
|
||||
?>
|
||||
<?php if ($object->stats->services_pending): ?>
|
||||
<span class="state pending<?= $currentUrl === $selfUrl->with('service_state', 99)->getRelativeUrl() ? ' active' : ''; ?>">
|
||||
<span class="state pending">
|
||||
<?= $this->qlink(
|
||||
$object->stats->services_pending,
|
||||
$selfUrl,
|
||||
array('service_state' => 99),
|
||||
array('title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state PENDING on host %s',
|
||||
'List %u services which are currently in state PENDING on host %s',
|
||||
$object->stats->services_pending
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translatePlural(
|
||||
'List %u service that is currently in state PENDING on host %s',
|
||||
'List %u services which are currently in state PENDING on host %s',
|
||||
$object->stats->services_pending
|
||||
),
|
||||
$object->stats->services_pending,
|
||||
$object->host_name
|
||||
),
|
||||
$object->stats->services_pending,
|
||||
$object->host_name
|
||||
))
|
||||
'data-base-target' => '_next'
|
||||
)
|
||||
) ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@
|
||||
echo $this->qlink(
|
||||
$this->translate('Send notification'),
|
||||
'monitoring/host/send-custom-notification',
|
||||
array('host_name' => $object->getName()),
|
||||
array('host' => $object->getName()),
|
||||
array(
|
||||
'icon' => 'bell',
|
||||
'data-base-target' => '_self',
|
||||
@ -23,7 +23,7 @@
|
||||
echo $this->qlink(
|
||||
$this->translate('Send notification'),
|
||||
'monitoring/service/send-custom-notification',
|
||||
array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()),
|
||||
array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
|
||||
array(
|
||||
'icon' => 'bell',
|
||||
'data-base-target' => '_self',
|
||||
|
@ -1,8 +0,0 @@
|
||||
<div class="controls">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->tabs; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->render('partials/host/object-header.phtml') ?>
|
||||
<?= $this->render('partials/host/servicesummary.phtml') ?>
|
||||
</div>
|
||||
<?= $services ?>
|
@ -132,8 +132,8 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
|
||||
<?php endforeach ?>
|
||||
<a aria-hidden="true" id="end" href="<?= Url::fromRequest()->remove(
|
||||
array(
|
||||
'raw_timestamp<',
|
||||
'raw_timestamp>'
|
||||
'timestamp<',
|
||||
'timestamp>'
|
||||
)
|
||||
)->overwriteParams(
|
||||
array(
|
||||
|
@ -3,7 +3,8 @@
|
||||
## Abstract
|
||||
|
||||
The instance.ini defines how icingaweb accesses the command pipe of your icinga process in order to submit external
|
||||
commands. When you are at the root of your icingaweb installation you can find it under ./config/modules/monitoring/instances.ini.
|
||||
commands. Depending on the config path (default: /etc/icingaweb2) of your icingaweb installation you can find it
|
||||
under ./modules/monitoring/instances.ini.
|
||||
|
||||
## Syntax
|
||||
|
||||
@ -33,5 +34,22 @@ setup key authentication at the endpoint and allow your icingweb's user to acces
|
||||
port=22 ; the port to use (22 if none is given)
|
||||
user=jdoe ; the user to authenticate with
|
||||
|
||||
You can also make use of the ssh resource for accessing an icinga pipe with key-based authentication, which will give
|
||||
you the possibility to define the location of the private key for a specific user, let's have a look:
|
||||
|
||||
[icinga]
|
||||
path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found
|
||||
host=my.remote.machine.com ; the hostname or address of the remote machine
|
||||
port=22 ; the port to use (22 if none is given)
|
||||
resource=ssh ; the ssh resource which contains the username and the location of the private key
|
||||
|
||||
And the associated ssh resource:
|
||||
|
||||
[ssh]
|
||||
type = "ssh"
|
||||
user = "ssh-user"
|
||||
private_key = "/etc/icingaweb2/ssh/ssh-user"
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
namespace Icinga\Module\Monitoring\Command\Transport;
|
||||
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Module\Monitoring\Command\Exception\TransportException;
|
||||
use Icinga\Module\Monitoring\Command\IcingaCommand;
|
||||
@ -44,6 +45,13 @@ class RemoteCommandFile implements CommandTransportInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Path to the private key file for the key-based authentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $privateKey;
|
||||
|
||||
/**
|
||||
* Path to the Icinga command file on the remote host
|
||||
*
|
||||
@ -137,6 +145,55 @@ class RemoteCommandFile implements CommandTransportInterface
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the private key file
|
||||
*
|
||||
* @param string $privateKey
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPrivateKey($privateKey)
|
||||
{
|
||||
$this->privateKey = (string) $privateKey;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the private key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrivateKey()
|
||||
{
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a given resource to set the user and the key
|
||||
*
|
||||
* @param string
|
||||
*
|
||||
* @throws ConfigurationError
|
||||
*/
|
||||
public function setResource($resource = null)
|
||||
{
|
||||
$config = ResourceFactory::getResourceConfig($resource);
|
||||
|
||||
if (! isset($config->user)) {
|
||||
throw new ConfigurationError(
|
||||
t("Can't send external Icinga Command. Remote user is missing")
|
||||
);
|
||||
}
|
||||
if (! isset($config->private_key)) {
|
||||
throw new ConfigurationError(
|
||||
t("Can't send external Icinga Command. The private key for the remote user is missing")
|
||||
);
|
||||
}
|
||||
|
||||
$this->setUser($config->user);
|
||||
$this->setPrivateKey($config->private_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the Icinga command file on the remote host
|
||||
*
|
||||
@ -192,6 +249,9 @@ class RemoteCommandFile implements CommandTransportInterface
|
||||
if (isset($this->user)) {
|
||||
$ssh .= sprintf(' -l %s', escapeshellarg($this->user));
|
||||
}
|
||||
if (isset($this->privateKey)) {
|
||||
$ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->privateKey));
|
||||
}
|
||||
$ssh .= sprintf(
|
||||
' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout
|
||||
escapeshellarg($this->host),
|
||||
|
@ -181,19 +181,6 @@ abstract class MonitoredObjectController extends Controller
|
||||
)
|
||||
);
|
||||
}
|
||||
$tabs->add(
|
||||
'services',
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translate('List all services on host %s'),
|
||||
$isService ? $object->getHost()->getName() : $object->getName()
|
||||
),
|
||||
'label' => $this->translate('Services'),
|
||||
'icon' => 'services',
|
||||
'url' => 'monitoring/show/services',
|
||||
'urlParams' => $params
|
||||
)
|
||||
);
|
||||
if ($this->backend->hasQuery('eventHistory')) {
|
||||
$tabs->add(
|
||||
'history',
|
||||
|
@ -1,5 +1,5 @@
|
||||
Module: monitoring
|
||||
Version: 2.0.0~alpha4
|
||||
Version: 2.0.0-rc1
|
||||
Description: Icinga monitoring module
|
||||
This is the core module for most Icingaweb users. It provides an
|
||||
abstraction layer for various Icinga data backends.
|
||||
|
@ -29,7 +29,7 @@ class ConfigCommand extends Command
|
||||
*
|
||||
* icingacli setup config directory
|
||||
*
|
||||
* icingacli setup config directory --mode 2775 --config /opt/icingaweb2/etc
|
||||
* icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc
|
||||
*/
|
||||
public function directoryAction()
|
||||
{
|
||||
@ -96,7 +96,7 @@ class ConfigCommand extends Command
|
||||
*
|
||||
* --path=<urlpath> The URL path to Icinga Web 2 [/icingaweb2]
|
||||
*
|
||||
* --root/--document-root=<directory> The directory from which the webserver will serve files [/path/to/icingaweb2/public]
|
||||
* --root|--document-root=<directory> The directory from which the webserver will serve files [/path/to/icingaweb2/public]
|
||||
*
|
||||
* --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
|
||||
*
|
||||
@ -106,9 +106,9 @@ class ConfigCommand extends Command
|
||||
*
|
||||
* icingacli setup config webserver apache
|
||||
*
|
||||
* icingacli setup config webserver apache --path /icingaweb2 --document-root /usr/share/icingaweb2/public --config=/etc/icingaweb2
|
||||
* icingacli setup config webserver apache --path=/icingaweb2 --document-root=/usr/share/icingaweb2/public --config=/etc/icingaweb2
|
||||
*
|
||||
* icingacli setup config webserver apache --file /etc/apache2/conf.d/icingaweb2.conf
|
||||
* icingacli setup config webserver apache --file=/etc/apache2/conf.d/icingaweb2.conf
|
||||
*
|
||||
* icingacli setup config webserver nginx
|
||||
*/
|
||||
|
@ -10,6 +10,39 @@ $configDir = Icinga::app()->getConfigDir();
|
||||
$setupTokenPath = rtrim($configDir, '/') . '/setup.token';
|
||||
$cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
|
||||
|
||||
$groupadd = null;
|
||||
$usermod = null;
|
||||
if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux')) {
|
||||
foreach (array(
|
||||
'groupadd -r icingaweb2' => array(
|
||||
'redhat', 'rhel', 'centos', 'fedora',
|
||||
'suse', 'sles', 'sled', 'opensuse'
|
||||
),
|
||||
'addgroup --system icingaweb2' => array('debian', 'ubuntu')
|
||||
) as $groupadd_ => $distros) {
|
||||
if (in_array($distro, $distros)) {
|
||||
$groupadd = $groupadd_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array(
|
||||
'usermod -a -G icingaweb2 apache' => array(
|
||||
'redhat', 'rhel', 'centos', 'fedora'
|
||||
),
|
||||
'usermod -A icingaweb2 wwwrun' => array(
|
||||
'suse', 'sles', 'sled', 'opensuse'
|
||||
),
|
||||
'usermod -a -G icingaweb2 www-data' => array(
|
||||
'debian', 'ubuntu'
|
||||
)
|
||||
) as $usermod_ => $distros) {
|
||||
if (in_array($distro, $distros)) {
|
||||
$usermod = $usermod_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="welcome-page">
|
||||
<h2><?= $this->translate('Welcome to the configuration of Icinga Web 2!') ?></h2>
|
||||
@ -51,6 +84,12 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
|
||||
<li><?= $this->translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?></li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
<?php if (! ($groupadd === null || $usermod === null)) { ?>
|
||||
<div class="code">
|
||||
<span><?= $this->escape($groupadd . ';') ?></span>
|
||||
<span><?= $this->escape($usermod . ';') ?></span>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<p><?= $this->translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?></p>
|
||||
<div class="code">
|
||||
<span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup config directory --group icingaweb2<?= $configDir !== '/etc/icingaweb2' ? ' --config ' . $configDir : ''; ?>;</span>
|
||||
|
6
modules/setup/module.info
Normal file
6
modules/setup/module.info
Normal file
@ -0,0 +1,6 @@
|
||||
Module: setup
|
||||
Version: 2.0.0-rc1
|
||||
Description: Setup module
|
||||
Web based wizard for setting up Icinga Web 2 and its modules.
|
||||
This includes the data backends (e.g. relational database, LDAP),
|
||||
the authentication method, where to store the user preferences and much more.
|
@ -41,7 +41,7 @@ class PhpCommand extends Command
|
||||
*
|
||||
* icingacli test php unit --verbose
|
||||
* icingacli test php unit --build
|
||||
* icingacli test php unit --include *SpecialTest
|
||||
* icingacli test php unit --include=*SpecialTest
|
||||
*/
|
||||
public function unitAction()
|
||||
{
|
||||
@ -109,8 +109,8 @@ class PhpCommand extends Command
|
||||
*
|
||||
* icingacli test php style --verbose
|
||||
* icingacli test php style --build
|
||||
* icingacli test php style --include path/to/your/file
|
||||
* icingacli test php style --exclude *someFile* --exclude someOtherFile*
|
||||
* icingacli test php style --include=path/to/your/file
|
||||
* icingacli test php style --exclude=*someFile* --exclude=someOtherFile*
|
||||
*/
|
||||
public function styleAction()
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
Module: test
|
||||
Version: 2.0.0~alpha4
|
||||
Version: 2.0.0-rc1
|
||||
Description: Translation module
|
||||
This module allows developers to run (unit) tests against Icinga Web 2 and
|
||||
any of its modules. Usually you do not need to enable this.
|
||||
|
@ -1,7 +1,7 @@
|
||||
Module: translation
|
||||
Version: 2.0.0~alpha4
|
||||
Version: 2.0.0-rc1
|
||||
Description: Translation module
|
||||
This module allows developers and translators to translate Icinga Web 2 and
|
||||
its modules for multiple languages. You do not need this module to run an
|
||||
internationalized web frontend. This is only for people who want to contribute
|
||||
translations or translate just their own moduls.
|
||||
translations or translate just their own modules.
|
||||
|
@ -210,6 +210,13 @@ textarea {
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
textarea.resource {
|
||||
&.ssh-identity {
|
||||
width: 50%;
|
||||
height: 25em;
|
||||
}
|
||||
}
|
||||
|
||||
form .description {
|
||||
font-size: 0.8em;
|
||||
margin: 0.3em 0 0 0.6em;
|
||||
|
@ -336,8 +336,7 @@ html {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* TODO: replace this with .error */
|
||||
.fileNotReadable {
|
||||
.message-error {
|
||||
padding: 0.5em;
|
||||
background-color: @colorCritical;
|
||||
font-weight: bold;
|
||||
|
@ -304,9 +304,6 @@ div.content.users {
|
||||
}
|
||||
|
||||
div.controls div.user-header {
|
||||
border-bottom: 2px solid @colorPetrol;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.user-name {
|
||||
display: inline-block;
|
||||
margin: 0 0 0.3em;
|
||||
@ -370,9 +367,6 @@ div.content.groups {
|
||||
}
|
||||
|
||||
div.controls div.group-header {
|
||||
border-bottom: 2px solid @colorPetrol;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.group-name {
|
||||
display: inline-block;
|
||||
margin: 0 0 0.3em;
|
||||
|
@ -27,10 +27,9 @@ class LdapBackendFormTest extends BaseTestCase
|
||||
*/
|
||||
public function testValidBackendIsValid()
|
||||
{
|
||||
$this->setUpResourceFactoryMock();
|
||||
Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
|
||||
->shouldReceive('assertAuthenticationPossible')->andReturnNull()
|
||||
->shouldReceive('setConfig')->andReturnNull();
|
||||
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
|
||||
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andReturnNull();
|
||||
$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));
|
||||
@ -53,9 +52,9 @@ class LdapBackendFormTest extends BaseTestCase
|
||||
*/
|
||||
public function testInvalidBackendIsNotValid()
|
||||
{
|
||||
$this->setUpResourceFactoryMock();
|
||||
Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
|
||||
->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException);
|
||||
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
|
||||
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException);
|
||||
$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));
|
||||
@ -72,12 +71,10 @@ class LdapBackendFormTest extends BaseTestCase
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUpResourceFactoryMock()
|
||||
protected function setUpUserBackendMock($ldapUserBackendMock)
|
||||
{
|
||||
Mockery::mock('alias:Icinga\Data\ResourceFactory')
|
||||
->shouldReceive('createResource')
|
||||
->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection'))
|
||||
->shouldReceive('getResourceConfig')
|
||||
->andReturn(new ConfigObject());
|
||||
Mockery::mock('alias:Icinga\Authentication\User\UserBackend')
|
||||
->shouldReceive('create')
|
||||
->andReturn($ldapUserBackendMock);
|
||||
}
|
||||
}
|
||||
|
@ -719,6 +719,34 @@ EOD;
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherLinebreaksAreRemoved()
|
||||
{
|
||||
$target = $this->writeConfigToTemporaryFile('');
|
||||
$writer = new IniWriter(
|
||||
array(
|
||||
'config' => Config::fromArray(
|
||||
array(
|
||||
'section' => array(
|
||||
'foo' => 'linebreak
|
||||
in line',
|
||||
'linebreak
|
||||
inkey' => 'blarg'
|
||||
)
|
||||
)
|
||||
),
|
||||
'filename' => $target
|
||||
)
|
||||
);
|
||||
|
||||
$rendered = $writer->render();
|
||||
var_dump($rendered);
|
||||
$this->assertEquals(
|
||||
count(explode("\n", $rendered)),
|
||||
4,
|
||||
'generated config should not contain more than three line breaks'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a INI-configuration string to a temporary file and return its path
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user