Skip to content
123 changes: 121 additions & 2 deletions application/controllers/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

namespace Icinga\Controllers;

use DateTime;
use Exception;
use Icinga\Application\Version;
use Icinga\Common\Database;
use Icinga\Forms\Security\RoleForm;
use Icinga\Model\Role;
use Icinga\Util\StringHelper;
use InvalidArgumentException;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
Expand All @@ -23,12 +28,18 @@
use Icinga\Web\Notification;
use Icinga\Web\Url;
use Icinga\Web\Widget;
use ipl\Sql\Connection;
use ipl\Sql\Insert;
use ipl\Sql\Select;
use ipl\Sql\Update;

/**
* Application and module configuration
*/
class ConfigController extends Controller
{
use Database;

/**
* Create and return the tabs to display when showing application configuration
*/
Expand Down Expand Up @@ -99,13 +110,21 @@ public function generalAction()
$form->setOnSuccess(function (GeneralConfigForm $form) {
$config = Config::app();
$useStrictCsp = (bool) $config->get('security', 'use_strict_csp', false);
$storeRolesInDb = (bool) $config->get('global', 'store_roles_in_db', false);
if ($form->onSuccess() === false) {
return false;
}

$appConfigForm = $form->getSubForm('form_config_general_application');
if ($appConfigForm && (bool) $appConfigForm->getValue('security_use_strict_csp') !== $useStrictCsp) {
$this->getResponse()->setReloadWindow(true);

if ($appConfigForm) {
if ((bool) $appConfigForm->getValue('security_use_strict_csp') !== $useStrictCsp) {
$this->getResponse()->setReloadWindow(true);
}

if (! $storeRolesInDb && $appConfigForm->getValue('global_store_roles_in_db')) {
$this->migrateRolesToFreshDb();
}
}
})->handleRequest();

Expand All @@ -114,6 +133,106 @@ public function generalAction()
$this->createApplicationTabs()->activate('general');
}

/**
* Migrate roles.ini to database if the latter contains no roles
*/
private function migrateRolesToFreshDb(): void
{
$roles = Config::app('roles');
$now = (new DateTime())->getTimestamp() * 1000;

$this->getDb()->transaction(function (Connection $db) use ($roles, $now) {
if (Role::on($db)->columns('id')->first()) {
return;
}

foreach ($roles as $name => $role) {
$db->prepexec(
(new Insert())
->into('icingaweb_role')
->columns(['name', 'unrestricted', 'ctime'])
->values([$name, $role->unrestricted ? 'y' : 'n', $now])
);

$id = $db->lastInsertId();
$permissions = StringHelper::trimSplit($role->permissions);
$refusals = StringHelper::trimSplit($role->refusals);
$permissionsAndRefusals = [];

foreach (StringHelper::trimSplit($role->users) as $user) {
$db->prepexec(
(new Insert())
->into('icingaweb_role_user')
->columns(['role_id', 'user_name'])
->values([$id, $user])
);
}

foreach (StringHelper::trimSplit($role->groups) as $group) {
$db->prepexec(
(new Insert())
->into('icingaweb_role_group')
->columns(['role_id', 'group_name'])
->values([$id, $group])
);
}

foreach ([$permissions, $refusals] as $permissionsOrRefusals) {
foreach ($permissionsOrRefusals as $permissionOrRefusal) {
$permissionsAndRefusals[$permissionOrRefusal] = ['allowed' => 'n', 'denied' => 'n'];
}
}

foreach ($permissions as $permission) {
$permissionsAndRefusals[$permission]['allowed'] = 'y';
}

foreach ($refusals as $refusal) {
$permissionsAndRefusals[$refusal]['denied'] = 'y';
}

foreach ($permissionsAndRefusals as $permission => $authz) {
$db->prepexec(
(new Insert())
->into('icingaweb_role_permission')
->columns(['role_id', 'permission', 'allowed', 'denied'])
->values([$id, $permission, $authz['allowed'], $authz['denied']])
);
}

foreach (RoleForm::collectProvidedPrivileges()[1] as $restrictionList) {
foreach ($restrictionList as $restriction => $_) {
if (isset($role->$restriction)) {
$db->prepexec(
(new Insert())
->into('icingaweb_role_restriction')
->columns(['role_id', 'restriction', 'filter'])
->values([$id, $restriction, $role->$restriction])
);
}
}
}
}

foreach ($roles as $name => $role) {
if (isset($role->parent)) {
$db->prepexec(
(new Update())
->table('icingaweb_role')
->set([
'parent_id' => (new Select())
->from('icingaweb_role')
->where(['name = ?' => $role->parent])
->columns(['id'])
,
])
->where(['name = ?' => $name])
);
}
}
});
}

/**
* Display the list of all modules
*/
Expand Down
120 changes: 95 additions & 25 deletions application/controllers/RoleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,28 @@

use Exception;
use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Application\Config;
use Icinga\Authentication\AdmissionLoader;
use Icinga\Authentication\Auth;
use Icinga\Authentication\RolesConfig;
use Icinga\Authentication\User\DomainAwareInterface;
use Icinga\Common\Database;
use Icinga\Data\Selectable;
use Icinga\Exception\NotFoundError;
use Icinga\Forms\Security\RoleDbForm;
use Icinga\Forms\Security\RoleForm;
use Icinga\Model\Role;
use Icinga\Repository\Repository;
use Icinga\Security\SecurityException;
use Icinga\User;
use Icinga\Web\Controller\AuthBackendController;
use Icinga\Web\View\PrivilegeAudit;
use Icinga\Web\Widget\RolesTable;
use Icinga\Web\Widget\SingleValueSearchControl;
use ipl\Html\Html;
use ipl\Html\HtmlString;
use ipl\Web\Compat\SearchControls;
use ipl\Web\Filter\QueryString;
use ipl\Web\Url;
use ipl\Web\Widget\Link;

Expand All @@ -28,6 +35,9 @@
*/
class RoleController extends AuthBackendController
{
use Database;
use SearchControls;

public function init()
{
$this->assertPermission('config/access-control/roles');
Expand Down Expand Up @@ -55,35 +65,85 @@ public function indexAction()
public function listAction()
{
$this->createListTabs()->activate('role/list');
$this->view->roles = (new RolesConfig())
->select();

$sortAndFilterColumns = [
'name' => $this->translate('Name'),
'users' => $this->translate('Users'),
'groups' => $this->translate('Groups'),
'parent' => $this->translate('Inherits From')
];

$this->setupFilterControl(
$this->view->roles,
$sortAndFilterColumns + [
'permissions' => $this->translate('Permissions')
]
);
$this->setupLimitControl();
$this->setupPaginationControl($this->view->roles);
$this->setupSortControl($sortAndFilterColumns, $this->view->roles);

if (Config::app()->get('global', 'store_roles_in_db')) {
$db = $this->getDb();
$query = Role::on($db)->with('parent');

$limitControl = $this->createLimitControl();
$sortControl = $this->createSortControl($query, ['name' => $this->translate('Name')]);
$paginationControl = $this->createPaginationControl($query);
$searchBar = $this->createSearchBar($query, [$limitControl->getLimitParam(), $sortControl->getSortParam()]);

if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = QueryString::parse((string) $this->params);
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();

return;
}
} else {
$filter = $searchBar->getFilter();
}

$query->filter($filter);

$this->addControl($paginationControl);
$this->addControl($limitControl);
$this->addControl($sortControl);
$this->addControl($searchBar);

$this->addControl(Html::tag(
'a',
[
'href' => Url::fromPath('role/add'),
'data-base-target' => '_next',
'class' => 'button-link icon-plus'
],
$this->translate('Create a New Role')
));

if ($query->count()) {
$this->addContent((new RolesTable())->setRoles($query));
} else {
$this->addContent(Html::tag('p', $this->translate('No roles found.')));
}

if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate();
}
} else {
$this->view->roles = (new RolesConfig())->select();

$sortAndFilterColumns = [
'name' => $this->translate('Name'),
'users' => $this->translate('Users'),
'groups' => $this->translate('Groups'),
'parent' => $this->translate('Inherits From')
];

$this->setupFilterControl(
$this->view->roles,
$sortAndFilterColumns + [
'permissions' => $this->translate('Permissions')
]
);

$this->setupLimitControl();
$this->setupPaginationControl($this->view->roles);
$this->setupSortControl($sortAndFilterColumns, $this->view->roles);
}
}

/**
* Create a new role
*/
public function addAction()
{
$role = new RoleForm();
$role = $this->prepareForm();
$role->setRedirectUrl('__CLOSE__');
$role->setRepository(new RolesConfig());
$role->setSubmitLabel($this->translate('Create Role'));
$role->add()->handleRequest();

Expand All @@ -96,9 +156,8 @@ public function addAction()
public function editAction()
{
$name = $this->params->getRequired('role');
$role = new RoleForm();
$role = $this->prepareForm();
$role->setRedirectUrl('__CLOSE__');
$role->setRepository(new RolesConfig());
$role->setSubmitLabel($this->translate('Update Role'));
$role->edit($name);

Expand All @@ -117,9 +176,8 @@ public function editAction()
public function removeAction()
{
$name = $this->params->getRequired('role');
$role = new RoleForm();
$role = $this->prepareForm();
$role->setRedirectUrl('__CLOSE__');
$role->setRepository(new RolesConfig());
$role->setSubmitLabel($this->translate('Remove Role'));
$role->remove($name);

Expand Down Expand Up @@ -386,4 +444,16 @@ protected function createListTabs()

return $tabs;
}

/**
* Create a form for role addition/modification/deletion and set the storage
*
* @return RoleForm
*/
private function prepareForm(): RoleForm
{
return Config::app()->get('global', 'store_roles_in_db')
? (new RoleDbForm())->setDb($this->getDb())
: (new RoleForm())->setRepository(new RolesConfig());
}
}
11 changes: 11 additions & 0 deletions application/forms/Config/General/ApplicationConfigForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ public function createElements(array $formData)
)
);

$this->addElement(
'checkbox',
'global_store_roles_in_db',
[
'label' => $this->translate('Store Roles in Database'),
'description' => $this->translate(
'Set whether to store roles used for access control in the database selected above.'
)
]
);

return $this;
}
}
Loading
Loading