diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
index 8f5f9eb42..73d40261c 100644
--- a/application/controllers/ConfigController.php
+++ b/application/controllers/ConfigController.php
@@ -24,14 +24,17 @@ class ConfigController extends ActionController
public function init()
{
$this->view->tabs = Widget::create('tabs')->add('index', array(
- 'title' => 'Application',
+ 'title' => $this->translate('Application'),
'url' => 'config'
))->add('authentication', array(
- 'title' => 'Authentication',
+ 'title' => $this->translate('Authentication'),
'url' => 'config/authentication'
))->add('resources', array(
- 'title' => 'Resources',
+ 'title' => $this->translate('Resources'),
'url' => 'config/resource'
+ ))->add('permissions', array(
+ 'title' => $this->translate('Permissions'),
+ 'url' => 'permissions'
));
}
diff --git a/application/controllers/PermissionsController.php b/application/controllers/PermissionsController.php
new file mode 100644
index 000000000..8709a28ad
--- /dev/null
+++ b/application/controllers/PermissionsController.php
@@ -0,0 +1,150 @@
+view->tabs = Widget::create('tabs')->add('index', array(
+ 'title' => $this->translate('Application'),
+ 'url' => 'config'
+ ))->add('authentication', array(
+ 'title' => $this->translate('Authentication'),
+ 'url' => 'config/authentication'
+ ))->add('resources', array(
+ 'title' => $this->translate('Resources'),
+ 'url' => 'config/resource'
+ ))->add('permissions', array(
+ 'title' => $this->translate('Permissions'),
+ 'url' => 'permissions'
+ ));
+ }
+
+ public function indexAction()
+ {
+ $this->view->tabs->activate('permissions');
+ $this->view->roles = Config::app('roles', true);
+ }
+
+ public function newAction()
+ {
+ $role = new RoleForm(array(
+ 'onSuccess' => function (RoleForm $role) {
+ $name = $role->getElement('name')->getValue();
+ $values = $role->getValues();
+ try {
+ $role->add($name, $values);
+ } catch (InvalidArgumentException $e) {
+ $role->addError($e->getMessage());
+ return false;
+ }
+ if ($role->save()) {
+ Notification::success(t('Role created'));
+ return true;
+ }
+ return false;
+ }
+ ));
+ $role
+ ->setSubmitLabel($this->translate('Create Role'))
+ ->setIniConfig(Config::app('roles', true))
+ ->setRedirectUrl('security')
+ ->handleRequest();
+ $this->view->form = $role;
+ }
+
+ public function updateAction()
+ {
+ $name = $this->_request->getParam('role');
+ if (empty($name)) {
+ throw new Zend_Controller_Action_Exception(
+ sprintf($this->translate('Required parameter \'%s\' missing'), 'role'),
+ 400
+ );
+ }
+ $role = new RoleForm();
+ $role->setSubmitLabel($this->translate('Update Role'));
+ try {
+ $role
+ ->setIniConfig(Config::app('roles', true))
+ ->load($name);
+ } catch (InvalidArgumentException $e) {
+ throw new Zend_Controller_Action_Exception(
+ $e->getMessage(),
+ 400
+ );
+ }
+ $role
+ ->setOnSuccess(function (RoleForm $role) use ($name) {
+ $oldName = $name;
+ $name = $role->getElement('name')->getValue();
+ $values = $role->getValues();
+ try {
+ $role->update($name, $values, $oldName);
+ } catch (InvalidArgumentException $e) {
+ $role->addError($e->getMessage());
+ return false;
+ }
+ if ($role->save()) {
+ Notification::success(t('Role updated'));
+ return true;
+ }
+ return false;
+ })
+ ->setRedirectUrl('security')
+ ->handleRequest();
+ $this->view->name = $name;
+ $this->view->form = $role;
+ }
+
+ public function removeAction()
+ {
+ $name = $this->_request->getParam('role');
+ if (empty($name)) {
+ throw new Zend_Controller_Action_Exception(
+ sprintf($this->translate('Required parameter \'%s\' missing'), 'role'),
+ 400
+ );
+ }
+ $role = new RoleForm();
+ try {
+ $role
+ ->setIniConfig(Config::app('roles', true))
+ ->load($name);
+ } catch (InvalidArgumentException $e) {
+ throw new Zend_Controller_Action_Exception(
+ $e->getMessage(),
+ 400
+ );
+ }
+ $confirmation = new ConfirmRemovalForm(array(
+ 'onSuccess' => function (ConfirmRemovalForm $confirmation) use ($name, $role) {
+ try {
+ $role->remove($name);
+ } catch (InvalidArgumentException $e) {
+ Notification::error($e->getMessage());
+ return false;
+ }
+ if ($role->save()) {
+ Notification::success(t('Role removed'));
+ return true;
+ }
+ return false;
+ }
+ ));
+ $confirmation
+ ->setSubmitLabel($this->translate('Remove Role'))
+ ->setRedirectUrl('security')
+ ->handleRequest();
+ $this->view->name = $name;
+ $this->view->form = $confirmation;
+ }
+}
diff --git a/application/forms/ConfigForm.php b/application/forms/ConfigForm.php
index fcf674cad..28d3111ea 100644
--- a/application/forms/ConfigForm.php
+++ b/application/forms/ConfigForm.php
@@ -5,6 +5,7 @@
namespace Icinga\Forms;
use Exception;
+use Zend_Form_Decorator_Abstract;
use Icinga\Web\Form;
use Icinga\Application\Config;
use Icinga\File\Ini\IniWriter;
@@ -58,7 +59,8 @@ class ConfigForm extends Form
'viewScript' => 'showConfiguration.phtml',
'errorMessage' => $e->getMessage(),
'configString' => $writer->render(),
- 'filePath' => $this->config->getConfigFile()
+ 'filePath' => $this->config->getConfigFile(),
+ 'placement' => Zend_Form_Decorator_Abstract::PREPEND
));
return false;
}
diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php
new file mode 100644
index 000000000..f81cc9fd9
--- /dev/null
+++ b/application/forms/Security/RoleForm.php
@@ -0,0 +1,225 @@
+getModuleManager()->getLoadedModules() as $module) {
+ foreach ($module->getProvidedPermissions() as $permission) {
+ /** @var object $permission */
+ $this->providedPermissions[$permission->name] = $permission->name . ': ' . $permission->description;
+ }
+ foreach ($module->getProvidedRestrictions() as $restriction) {
+ /** @var object $restriction */
+ $this->providedRestrictions[$restriction->name] = $restriction->name . ': ' . $restriction->description;
+ }
+ }
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => t('Role Name'),
+ 'description' => t('The name of the role')
+ ),
+ ),
+ array(
+ 'textarea',
+ 'users',
+ array(
+ 'label' => t('Users'),
+ 'description' => t('Comma-separated list of users that are assigned to the role')
+ ),
+ ),
+ array(
+ 'textarea',
+ 'groups',
+ array(
+ 'label' => t('Groups'),
+ 'description' => t('Comma-separated list of groups that are assigned to the role')
+ ),
+ ),
+ array(
+ 'multiselect',
+ 'permissions',
+ array(
+ 'label' => t('Permissions Set'),
+ 'description' => t('The permissions to grant. You may select more than one permission'),
+ 'multiOptions' => $this->providedPermissions
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * Load a role
+ *
+ * @param string $name The name of the role
+ *
+ * @return $this
+ *
+ * @throws LogicException If the config is not set
+ * @see ConfigForm::setConfig() For setting the config.
+ */
+ public function load($name)
+ {
+ if (! isset($this->config)) {
+ throw new LogicException(sprintf('Can\'t load role \'%s\'. Config is not set', $name));
+ }
+ if (! $this->config->hasSection($name)) {
+ throw new InvalidArgumentException(sprintf(
+ t('Can\'t load role \'%s\'. Role does not exist'),
+ $name
+ ));
+ }
+ $role = $this->config->getSection($name)->toArray();
+ $role['permissions'] = ! empty($role['permissions'])
+ ? String::trimSplit($role['permissions'])
+ : null;
+ $role['name'] = $name;
+ $this->populate($role);
+ return $this;
+ }
+
+ /**
+ * Add a role
+ *
+ * @param string $name The name of the role
+ * @param array $values
+ *
+ * @return $this
+ *
+ * @throws LogicException If the config is not set
+ * @throws InvalidArgumentException If the role to add already exists
+ * @see ConfigForm::setConfig() For setting the config.
+ */
+ public function add($name, array $values)
+ {
+ if (! isset($this->config)) {
+ throw new LogicException(sprintf('Can\'t add role \'%s\'. Config is not set', $name));
+ }
+ if ($this->config->hasSection($name)) {
+ throw new InvalidArgumentException(sprintf(
+ t('Can\'t add role \'%s\'. Role already exists'),
+ $name
+ ));
+ }
+ $this->config->setSection($name, $values);
+ return $this;
+ }
+
+ /**
+ * Remove a role
+ *
+ * @param string $name The name of the role
+ *
+ * @return $this
+ *
+ * @throws LogicException If the config is not set
+ * @throws InvalidArgumentException If the role does not exist
+ * @see ConfigForm::setConfig() For setting the config.
+ */
+ public function remove($name)
+ {
+ if (! isset($this->config)) {
+ throw new LogicException(sprintf('Can\'t remove role \'%s\'. Config is not set', $name));
+ }
+ if (! $this->config->hasSection($name)) {
+ throw new InvalidArgumentException(sprintf(
+ t('Can\'t remove role \'%s\'. Role does not exist'),
+ $name
+ ));
+ }
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Update a role
+ *
+ * @param string $name The possibly new name of the role
+ * @param array $values
+ * @param string $oldName The name of the role to update
+ *
+ * @return $this
+ *
+ * @throws LogicException If the config is not set
+ * @throws InvalidArgumentException If the role to update does not exist
+ * @see ConfigForm::setConfig() For setting the config.
+ */
+ public function update($name, array $values, $oldName)
+ {
+ if (! isset($this->config)) {
+ throw new LogicException(sprintf('Can\'t update role \'%s\'. Config is not set', $name));
+ }
+ if ($name !== $oldName) {
+ // The permission got a new name
+ $this->remove($oldName);
+ $this->add($name, $values);
+ } else {
+ if (! $this->config->hasSection($name)) {
+ throw new InvalidArgumentException(sprintf(
+ t('Can\'t update role \'%s\'. Role does not exist'),
+ $name
+ ));
+ }
+ $this->config->setSection($name, $values);
+ }
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::getValues() For the method documentation.
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $permissions = $this->getElement('permissions')->getValue();
+ return array(
+ 'users' => $this->getElement('users')->getValue(),
+ 'groups' => $this->getElement('groups')->getValue(),
+ 'permissions' => ! empty($permissions) ? implode(', ', $permissions) : null
+ );
+ }
+}
diff --git a/application/views/scripts/permissions/index.phtml b/application/views/scripts/permissions/index.phtml
new file mode 100644
index 000000000..285d2f894
--- /dev/null
+++ b/application/views/scripts/permissions/index.phtml
@@ -0,0 +1,45 @@
+
+ = $tabs ?>
+
+
+
+
= $this->translate('Permissions') ?>
+ isEmpty()): ?>
+ = $this->translate('No permissions found.') ?>
+
+
+
+
+ = $this->translate('Name') ?> |
+ = $this->translate('Permissions') ?> |
+ = $this->translate('Users') ?> |
+ = $this->translate('Groups') ?> |
+
+
+
+ $role): /** @var object $role */ ?>
+
+
+ = $this->escape($name) ?>
+
+ |
+ = $this->escape($role->permissions) ?> |
+ = $this->escape($role->users) ?> |
+ = $this->escape($role->groups) ?> |
+
+
+ = $this->translate('Remove role') ?>
+
+ |
+
+
+
+
+
+
+ = $this->translate('New Role') ?>
+
+
+
diff --git a/application/views/scripts/permissions/new.phtml b/application/views/scripts/permissions/new.phtml
new file mode 100644
index 000000000..d5f9e7e33
--- /dev/null
+++ b/application/views/scripts/permissions/new.phtml
@@ -0,0 +1,4 @@
+
+
= $this->translate('New Role') ?>
+ = $form ?>
+
diff --git a/application/views/scripts/permissions/remove.phtml b/application/views/scripts/permissions/remove.phtml
new file mode 100644
index 000000000..a9360e78f
--- /dev/null
+++ b/application/views/scripts/permissions/remove.phtml
@@ -0,0 +1,4 @@
+
+
= sprintf($this->translate('Remove Role %s'), $name) ?>
+ = $form ?>
+
diff --git a/application/views/scripts/permissions/update.phtml b/application/views/scripts/permissions/update.phtml
new file mode 100644
index 000000000..8cb235a59
--- /dev/null
+++ b/application/views/scripts/permissions/update.phtml
@@ -0,0 +1,4 @@
+
+
= sprintf($this->translate('Update Role %s'), $name) ?>
+ = $form ?>
+
diff --git a/application/views/scripts/showConfiguration.phtml b/application/views/scripts/showConfiguration.phtml
index 1dde3b16b..682b349c9 100644
--- a/application/views/scripts/showConfiguration.phtml
+++ b/application/views/scripts/showConfiguration.phtml
@@ -1,6 +1,5 @@
= $this->translate('Saving Configuration Failed'); ?>
-
= sprintf(
$this->translate('The file %s couldn\'t be stored. (Error: "%s")'),
@@ -25,4 +24,4 @@
= $this->escape($configString); ?>
-
\ No newline at end of file
+
diff --git a/library/Icinga/Application/Modules/Manager.php b/library/Icinga/Application/Modules/Manager.php
index 78e505d06..6469d3929 100644
--- a/library/Icinga/Application/Modules/Manager.php
+++ b/library/Icinga/Application/Modules/Manager.php
@@ -404,10 +404,9 @@ class Manager
}
/**
- * Return an array containing all loaded modules
+ * Get the currently loaded modules
*
- * @return array
- * @see Module
+ * @return Module[]
*/
public function getLoadedModules()
{
diff --git a/library/Icinga/Authentication/AdmissionLoader.php b/library/Icinga/Authentication/AdmissionLoader.php
index 65d99f427..d97fc534e 100644
--- a/library/Icinga/Authentication/AdmissionLoader.php
+++ b/library/Icinga/Authentication/AdmissionLoader.php
@@ -5,6 +5,7 @@
namespace Icinga\Authentication;
use Icinga\Application\Config;
+use Icinga\Application\Logger;
use Icinga\Exception\NotReadableError;
use Icinga\Data\ConfigObject;
use Icinga\User;
@@ -43,72 +44,46 @@ class AdmissionLoader
}
/**
- * Get user permissions
+ * Get user permissions and restrictions
*
- * @param User $user
+ * @param User $user
*
* @return array
*/
- public function getPermissions(User $user)
+ public function getPermissionsAndRestrictions(User $user)
{
$permissions = array();
+ $restrictions = array();
+ $username = $user->getUsername();
try {
- $config = Config::app('permissions');
+ $roles = Config::app('roles');
} catch (NotReadableError $e) {
Logger::error(
- 'Can\'t get permissions for user \'%s\'. An exception was thrown:',
- $user->getUsername(),
+ 'Can\'t get permissions and restrictions for user \'%s\'. An exception was thrown:',
+ $username,
$e
);
- return $permissions;
+ return array($permissions, $restrictions);
}
- $username = $user->getUsername();
$userGroups = $user->getGroups();
- foreach ($config as $section) {
- if (! empty($section->permissions)
- && $this->match($username, $userGroups, $section)
- ) {
+ foreach ($roles as $role) {
+ if ($this->match($username, $userGroups, $role)) {
$permissions = array_merge(
$permissions,
- array_diff(String::trimSplit($section->permissions), $permissions)
+ array_diff(String::trimSplit($role->permissions), $permissions)
);
+ $restrictionsFromRole = $role->toArray();
+ unset($restrictionsFromRole['users']);
+ unset($restrictionsFromRole['groups']);
+ unset($restrictionsFromRole['permissions']);
+ foreach ($restrictionsFromRole as $name => $restriction) {
+ if (! isset($restrictions[$name])) {
+ $restrictions[$name] = array();
+ }
+ $restrictions[$name][] = $restriction;
+ }
}
}
- return $permissions;
- }
-
- /**
- * Get user restrictions
- *
- * @param User $user
- *
- * @return array
- */
- public function getRestrictions(User $user)
- {
- $restrictions = array();
- try {
- $config = Config::app('restrictions');
- } catch (NotReadableError $e) {
- Logger::error(
- 'Can\'t get restrictions for user \'%s\'. An exception was thrown:',
- $user->getUsername(),
- $e
- );
- return $restrictions;
- }
- $username = $user->getUsername();
- $userGroups = $user->getGroups();
- foreach ($config as $section) {
- if (! empty($section->restriction)
- && $this->match($username, $userGroups, $section)
- ) {
- $restrictions = array_merge(
- $restrictions,
- array_diff(String::trimSplit($section->restriction), $restrictions)
- );
- }
- }
- return $restrictions;
+ return array($permissions, $restrictions);
}
}
diff --git a/library/Icinga/Authentication/Manager.php b/library/Icinga/Authentication/Manager.php
index 88258351e..0a9b40981 100644
--- a/library/Icinga/Authentication/Manager.php
+++ b/library/Icinga/Authentication/Manager.php
@@ -107,8 +107,9 @@ class Manager
}
$user->setGroups($groups);
$admissionLoader = new AdmissionLoader();
- $user->setPermissions($admissionLoader->getPermissions($user));
- $user->setRestrictions($admissionLoader->getRestrictions($user));
+ list($permissions, $restrictions) = $admissionLoader->getPermissionsAndRestrictions($user);
+ $user->setPermissions($permissions);
+ $user->setRestrictions($restrictions);
$this->user = $user;
if ($persist) {
$this->persistCurrentUser();
diff --git a/library/Icinga/File/Ini/IniWriter.php b/library/Icinga/File/Ini/IniWriter.php
index e3b757b78..94a20faa5 100644
--- a/library/Icinga/File/Ini/IniWriter.php
+++ b/library/Icinga/File/Ini/IniWriter.php
@@ -61,12 +61,14 @@ 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);
} else {
$oldconfig = new Zend_Config(array());
+ $content = '';
}
$newconfig = $this->_config;
- $editor = new IniEditor(@file_get_contents($this->_filename), $this->options);
+ $editor = new IniEditor($content, $this->options);
$this->diffConfigs($oldconfig, $newconfig, $editor);
$this->updateSectionOrder($newconfig, $editor);
return $editor->getText();
diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php
index 863d4f1c1..163dba455 100644
--- a/library/Icinga/Web/Form.php
+++ b/library/Icinga/Web/Form.php
@@ -46,7 +46,7 @@ class Form extends Zend_Form
/**
* The callback to call instead of Form::onSuccess()
*
- * @var Callback
+ * @var callable
*/
protected $onSuccess;
@@ -122,29 +122,11 @@ class Form extends Zend_Form
);
/**
- * Create a new form
- *
- * Accepts an additional option `onSuccess' which is a callback that is called instead of this
- * form's method. It is called using the following signature: (Form $form).
- *
- * @see Zend_Form::__construct()
- *
- * @throws LogicException In case `onSuccess' is not callable
+ * (non-PHPDoc)
+ * @see \Zend_Form::construct() For the method documentation.
*/
public function __construct($options = null)
{
- if (is_array($options) && isset($options['onSuccess'])) {
- $this->onSuccess = $options['onSuccess'];
- unset($options['onSuccess']);
- } elseif (isset($options->onSuccess)) {
- $this->onSuccess = $options->onSuccess;
- unset($options->onSuccess);
- }
-
- if ($this->onSuccess !== null && false === is_callable($this->onSuccess)) {
- throw new LogicException('The option `onSuccess\' is not callable');
- }
-
// Zend's plugin loader reverses the order of added prefix paths thus trying our paths first before trying
// Zend paths
$this->addPrefixPaths(array(
diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php
index 366067c09..d61e32858 100644
--- a/modules/monitoring/configuration.php
+++ b/modules/monitoring/configuration.php
@@ -2,14 +2,54 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
-/* @var $this \Icinga\Application\Modules\Module */
+/** @var $this \Icinga\Application\Modules\Module */
+
+$this->providePermission(
+ 'monitoring/command/*',
+ $this->translate('Allow all commands')
+);
+$this->providePermission(
+ 'monitoring/command/schedule*',
+ $this->translate('Allow all scheduling checks and downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/schedule-check',
+ $this->translate('Allow scheduling host and service checks')
+);
+$this->providePermission(
+ 'monitoring/command/schedule-downtime',
+ $this->translate('Allow scheduling host and service downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/acknowledge-problem',
+ $this->translate('Allow acknowledging host and service problems')
+);
+$this->providePermission(
+ 'monitoring/command/add-comment',
+ $this->translate('Allow commenting on hosts and services')
+);
+$this->providePermission(
+ 'monitoring/command/remove*',
+ $this->translate('Allow removing problem acknowledgements, host and service comments and downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/remove-acknowledgement',
+ $this->translate('Allow removing problem acknowledgements')
+);
+$this->providePermission(
+ 'monitoring/command/remove-comment',
+ $this->translate('Allow removing host and service comments')
+);
+$this->providePermission(
+ 'monitoring/command/remove-downtime',
+ $this->translate('Allow removing host and service downtimes')
+);
+
+$this->provideRestriction(
+ 'monitoring/filter',
+ $this->translate('Restrict views to the hosts and services that match the filter')
+);
-// TODO: We need to define a useful permission set for this module, the
-// list provided here is just an example
-$this->providePermission('commands/all', 'Allow to send all commands');
-$this->providePermission('commands/safe', 'Allow to to send a subset of "safe" commands');
-$this->providePermission('log', 'Allow full log access');
-$this->provideRestriction('filter', 'Filter accessible object');
$this->provideConfigTab('backends', array(
'title' => 'Backends',
'url' => 'config'