diff --git a/application/controllers/ContactGroupController.php b/application/controllers/ContactGroupController.php
index aec7a38e9..a22224389 100644
--- a/application/controllers/ContactGroupController.php
+++ b/application/controllers/ContactGroupController.php
@@ -24,7 +24,7 @@ class ContactGroupController extends CompatController
{
public function init(): void
{
- $this->assertPermission('notifications/config/contact-groups');
+ $this->assertPermission('notifications/config/contacts');
}
public function indexAction(): void
diff --git a/application/controllers/ContactGroupsController.php b/application/controllers/ContactGroupsController.php
index 0917c9ffa..3c0f18581 100644
--- a/application/controllers/ContactGroupsController.php
+++ b/application/controllers/ContactGroupsController.php
@@ -4,6 +4,7 @@
namespace Icinga\Module\Notifications\Controllers;
+use Icinga\Module\Notifications\Common\ConfigurationTabs;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Forms\ContactGroupForm;
@@ -28,10 +29,10 @@
use ipl\Web\Layout\MinimalItemLayout;
use ipl\Web\Widget\ActionLink;
use ipl\Web\Widget\ButtonLink;
-use ipl\Web\Widget\Tabs;
class ContactGroupsController extends CompatController
{
+ use ConfigurationTabs;
use SearchControls;
/** @var Filter\Rule Filter from query string parameters */
@@ -39,7 +40,7 @@ class ContactGroupsController extends CompatController
public function init(): void
{
- $this->assertPermission('notifications/config/contact-groups');
+ $this->assertPermission('notifications/config/contacts');
}
public function indexAction(): void
@@ -96,15 +97,23 @@ public function indexAction(): void
if (Channel::on(Database::get())->columns([new Expression('1')])->limit(1)->first() === null) {
$addButton->disable($this->translate('A channel is required to add a contact group'));
- $emptyStateMessage = TemplateString::create(
- // translators: %1$s will be replaced by a line break
- $this->translate(
- 'No contact groups found.%1$sTo add new contact group, please {{#link}}configure a'
- . ' Channel{{/link}} first.%1$sOnce done, you should proceed by creating your first contact.'
- ),
- ['link' => (new ActionLink(null, Links::channelAdd()))->setBaseTarget('_next')],
- [HtmlString::create('
')]
- );
+ if ($this->Auth()->hasPermission('config/modules')) {
+ $emptyStateMessage = TemplateString::create(
+ // translators: %1$s will be replaced by a line break
+ $this->translate(
+ 'No contact groups found.%1$s'
+ . 'To add new contact group, please {{#link}}configure a Channel{{/link}} first.%1$s'
+ . 'Once done, you should proceed by creating your first contact.'
+ ),
+ ['link' => (new ActionLink(null, Links::channelAdd()))->setBaseTarget('_next')],
+ [HtmlString::create('
')]
+ );
+ } else {
+ $emptyStateMessage = $this->translate(
+ 'No contact groups found. To add a new contact group, a channel is required.'
+ . ' Please contact your system administrator.'
+ );
+ }
} else {
$emptyStateMessage = TemplateString::create(
$this->translate(
@@ -195,28 +204,6 @@ public function suggestMemberAction(): void
$this->getDocument()->addHtml($members);
}
- public function getTabs(): Tabs
- {
- return parent::getTabs()
- ->add('schedules', [
- 'label' => $this->translate('Schedules'),
- 'url' => Links::schedules(),
- 'baseTarget' => '_main'
- ])->add('event-rules', [
- 'label' => $this->translate('Event Rules'),
- 'url' => Links::eventRules(),
- 'baseTarget' => '_main'
- ])->add('contacts', [
- 'label' => $this->translate('Contacts'),
- 'url' => Links::contacts(),
- 'baseTarget' => '_main'
- ])->add('contact-groups', [
- 'label' => $this->translate('Contact Groups'),
- 'url' => Links::contactGroups(),
- 'baseTarget' => '_main'
- ]);
- }
-
/**
* Get the filter created from query string parameters
*
diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php
index 2fd3640a2..2e427b7cb 100644
--- a/application/controllers/ContactsController.php
+++ b/application/controllers/ContactsController.php
@@ -4,6 +4,7 @@
namespace Icinga\Module\Notifications\Controllers;
+use Icinga\Module\Notifications\Common\ConfigurationTabs;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Model\Channel;
use Icinga\Module\Notifications\View\ContactRenderer;
@@ -29,6 +30,7 @@
class ContactsController extends CompatController
{
+ use ConfigurationTabs;
use SearchControls;
/** @var Connection */
@@ -96,12 +98,19 @@ public function indexAction()
if (Channel::on($this->db)->columns([new Expression('1')])->limit(1)->first() === null) {
$addButton->disable($this->translate('A channel is required to add a contact'));
- $emptyStateMessage = TemplateString::create(
- $this->translate(
- 'No contacts found. To add a new contact, please {{#link}}configure a Channel{{/link}} first.'
- ),
- ['link' => (new ActionLink(null, Links::channelAdd()))->setBaseTarget('_next')]
- );
+ if ($this->Auth()->hasPermission('config/modules')) {
+ $emptyStateMessage = TemplateString::create(
+ $this->translate(
+ 'No contacts found. To add a new contact, please {{#link}}configure a Channel{{/link}} first.'
+ ),
+ ['link' => (new ActionLink(null, Links::channelAdd()))->setBaseTarget('_next')]
+ );
+ } else {
+ $emptyStateMessage = $this->translate(
+ 'No contacts found. To add a new contact, a channel is required.'
+ . ' Please contact your system administrator.'
+ );
+ }
}
$this->addContent($addButton);
@@ -171,30 +180,4 @@ protected function getFilter(): Filter\Rule
return $this->filter;
}
-
- public function getTabs()
- {
- if ($this->getRequest()->getActionName() === 'index') {
- return parent::getTabs()
- ->add('schedules', [
- 'label' => $this->translate('Schedules'),
- 'url' => Links::schedules(),
- 'baseTarget' => '_main'
- ])->add('event-rules', [
- 'label' => $this->translate('Event Rules'),
- 'url' => Links::eventRules(),
- 'baseTarget' => '_main'
- ])->add('contacts', [
- 'label' => $this->translate('Contacts'),
- 'url' => Links::contacts(),
- 'baseTarget' => '_main'
- ])->add('contact-groups', [
- 'label' => $this->translate('Contact Groups'),
- 'url' => Links::contactGroups(),
- 'baseTarget' => '_main'
- ]);
- }
-
- return parent::getTabs();
- }
}
diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php
index 9f61cffc7..d15526751 100644
--- a/application/controllers/EventRuleController.php
+++ b/application/controllers/EventRuleController.php
@@ -39,7 +39,7 @@ class EventRuleController extends CompatController
public function init(): void
{
- $this->assertPermission('notifications/config/event-rule');
+ $this->assertPermission('notifications/config/event-rules');
$this->session = Session::getSession()->getNamespace('notifications.event-rule');
}
diff --git a/application/controllers/EventRulesController.php b/application/controllers/EventRulesController.php
index 70e1db13b..07c938003 100644
--- a/application/controllers/EventRulesController.php
+++ b/application/controllers/EventRulesController.php
@@ -4,6 +4,7 @@
namespace Icinga\Module\Notifications\Controllers;
+use Icinga\Module\Notifications\Common\ConfigurationTabs;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Forms\EventRuleForm;
@@ -28,6 +29,7 @@
class EventRulesController extends CompatController
{
+ use ConfigurationTabs;
use SearchControls;
public function init()
@@ -159,30 +161,4 @@ public function searchEditorAction(): void
$this->getDocument()->add($editor);
$this->setTitle($this->translate('Adjust Filter'));
}
-
- public function getTabs()
- {
- if ($this->getRequest()->getActionName() === 'index') {
- return parent::getTabs()
- ->add('schedules', [
- 'label' => $this->translate('Schedules'),
- 'url' => Links::schedules(),
- 'baseTarget' => '_main'
- ])->add('event-rules', [
- 'label' => $this->translate('Event Rules'),
- 'url' => Links::eventRules(),
- 'baseTarget' => '_main'
- ])->add('contacts', [
- 'label' => $this->translate('Contacts'),
- 'url' => Links::contacts(),
- 'baseTarget' => '_main'
- ])->add('contact-groups', [
- 'label' => $this->translate('Contact Groups'),
- 'url' => Links::contactGroups(),
- 'baseTarget' => '_main'
- ]);
- }
-
- return parent::getTabs();
- }
}
diff --git a/application/controllers/ScheduleController.php b/application/controllers/ScheduleController.php
index d9e0b1204..177f9a028 100644
--- a/application/controllers/ScheduleController.php
+++ b/application/controllers/ScheduleController.php
@@ -30,6 +30,8 @@ class ScheduleController extends CompatController
public function init(): void
{
+ $this->assertPermission('notifications/config/schedules');
+
parent::init();
$this->session = Session::getSession()->getNamespace('notifications.schedule');
diff --git a/application/controllers/SchedulesController.php b/application/controllers/SchedulesController.php
index a9c212486..e07d079d2 100644
--- a/application/controllers/SchedulesController.php
+++ b/application/controllers/SchedulesController.php
@@ -4,6 +4,7 @@
namespace Icinga\Module\Notifications\Controllers;
+use Icinga\Module\Notifications\Common\ConfigurationTabs;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Model\Schedule;
@@ -17,15 +18,20 @@
use ipl\Web\Control\SortControl;
use ipl\Web\Filter\QueryString;
use ipl\Web\Widget\ButtonLink;
-use ipl\Web\Widget\Tabs;
class SchedulesController extends CompatController
{
+ use ConfigurationTabs;
use SearchControls;
/** @var Filter\Rule Filter from query string parameters */
private $filter;
+ public function init(): void
+ {
+ $this->assertPermission('notifications/config/schedules');
+ }
+
public function indexAction(): void
{
$schedules = Schedule::on(Database::get());
@@ -102,28 +108,6 @@ public function searchEditorAction(): void
$this->setTitle(t('Adjust Filter'));
}
- public function getTabs(): Tabs
- {
- return parent::getTabs()
- ->add('schedules', [
- 'label' => $this->translate('Schedules'),
- 'url' => Links::schedules(),
- 'baseTarget' => '_main'
- ])->add('event-rules', [
- 'label' => $this->translate('Event Rules'),
- 'url' => Links::eventRules(),
- 'baseTarget' => '_main'
- ])->add('contacts', [
- 'label' => $this->translate('Contacts'),
- 'url' => Links::contacts(),
- 'baseTarget' => '_main'
- ])->add('contact-groups', [
- 'label' => $this->translate('Contact Groups'),
- 'url' => Links::contactGroups(),
- 'baseTarget' => '_main'
- ]);
- }
-
/**
* Get the filter created from query string parameters
*
diff --git a/configuration.php b/configuration.php
index 439e09da3..a26fd7014 100644
--- a/configuration.php
+++ b/configuration.php
@@ -3,6 +3,7 @@
/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
use Icinga\Application\Modules\Module;
+use Icinga\Authentication\Auth;
/** @var Module $this */
@@ -14,14 +15,30 @@
]
);
-$section->add(
- N_('Configuration'),
- [
- 'icon' => 'wrench',
- 'description' => $this->translate('Configuration'),
- 'url' => 'notifications/schedules'
- ]
-);
+$auth = Auth::getInstance();
+$authenticated = $auth->getUser() !== null;
+
+$configLandingPage = null;
+if ($authenticated) {
+ if ($auth->hasPermission('notifications/config/schedules')) {
+ $configLandingPage = 'notifications/schedules';
+ } elseif ($auth->hasPermission('notifications/config/event-rules')) {
+ $configLandingPage = 'notifications/event-rules';
+ } elseif ($auth->hasPermission('notifications/config/contacts')) {
+ $configLandingPage = 'notifications/contacts';
+ }
+}
+
+if ($configLandingPage !== null) {
+ $section->add(
+ N_('Configuration'),
+ [
+ 'icon' => 'wrench',
+ 'description' => $this->translate('Configuration'),
+ 'url' => $configLandingPage
+ ]
+ );
+}
$section->add(
N_('Events'),
@@ -32,14 +49,24 @@
]
);
+$this->providePermission(
+ 'notifications/config/schedules',
+ $this->translate('Allow to configure schedules')
+);
+
$this->providePermission(
'notifications/config/event-rules',
$this->translate('Allow to configure event rules')
);
$this->providePermission(
- 'notifications/config/contact-groups',
- $this->translate('Allow to configure contact groups')
+ 'notifications/config/contacts',
+ $this->translate('Allow to configure contacts and contact groups')
+);
+
+$this->providePermission(
+ 'notifications/view/contacts',
+ $this->translate('Allow to view contacts')
);
$this->providePermission(
@@ -47,10 +74,10 @@
$this->translate('Allow to modify configuration via API')
);
-$this->provideRestriction(
- 'notifications/filter/objects',
- $this->translate('Restrict access to the objects that match the filter')
-);
+//$this->provideRestriction(
+// 'notifications/filter/objects',
+// $this->translate('Restrict access to the objects that match the filter')
+//);
$this->provideConfigTab(
'database',
diff --git a/doc/03-Configuration.md b/doc/03-Configuration.md
index d0da5e9be..e4b4b4ef2 100644
--- a/doc/03-Configuration.md
+++ b/doc/03-Configuration.md
@@ -18,6 +18,24 @@ If you just installed Icinga Notifications Web, remember to activate it on your
+## Access Control
+
+!!! warning
+
+ Authorization mechanics in the current release are not fully functional. For example, restricting users to certain
+ objects is not supported. Do not grant users access to the module unless you are sure they are authorized to see
+ **all** events and incidents.
+
+### Permissions
+
+| Permission | Description |
+|----------------------------------|------------------------------------------------|
+| notifications/config/schedules | Allow to configure schedules |
+| notifications/config/event-rules | Allow to configure event rules |
+| notifications/config/contacts | Allow to configure contacts and contact groups |
+| notifications/view/contacts | Allow to view contacts |
+| notifications/api | Allow to modify configuration via API |
+
## Database Configuration
Connection configuration for the database, which both,
diff --git a/library/Notifications/Common/Auth.php b/library/Notifications/Common/Auth.php
index f718d7ebe..c3a56e377 100644
--- a/library/Notifications/Common/Auth.php
+++ b/library/Notifications/Common/Auth.php
@@ -26,6 +26,10 @@ public function getAuth(): IcingaAuth
*/
public function applyRestrictions(Query $query): void
{
+ // TODO: Since the recent integration rewrite, restriction support does not work anymore as expected.
+ // Will be reworked with the next release.
+ return;
+
/** @var User $user */
$user = $this->getAuth()->getUser();
if ($user->isUnrestricted()) {
diff --git a/library/Notifications/Common/ConfigurationTabs.php b/library/Notifications/Common/ConfigurationTabs.php
new file mode 100644
index 000000000..c7771c3fc
--- /dev/null
+++ b/library/Notifications/Common/ConfigurationTabs.php
@@ -0,0 +1,55 @@
+getRequest()->getActionName() === 'index') {
+ if ($this->Auth()->hasPermission('notifications/config/schedules')) {
+ $tabs->add('schedules', [
+ 'label' => $this->translate('Schedules'),
+ 'url' => Links::schedules(),
+ 'baseTarget' => '_main'
+ ]);
+ }
+
+ if ($this->Auth()->hasPermission('notifications/config/event-rules')) {
+ $tabs->add('event-rules', [
+ 'label' => $this->translate('Event Rules'),
+ 'url' => Links::eventRules(),
+ 'baseTarget' => '_main'
+ ]);
+ }
+
+ if ($this->Auth()->hasPermission('notifications/config/contacts')) {
+ $tabs->add('contacts', [
+ 'label' => $this->translate('Contacts'),
+ 'url' => Links::contacts(),
+ 'baseTarget' => '_main'
+ ])->add('contact-groups', [
+ 'label' => $this->translate('Contact Groups'),
+ 'url' => Links::contactGroups(),
+ 'baseTarget' => '_main'
+ ]);
+ }
+ }
+
+ return $tabs;
+ }
+}
diff --git a/library/Notifications/View/IncidentContactRenderer.php b/library/Notifications/View/IncidentContactRenderer.php
index 73b043e60..f6324d664 100644
--- a/library/Notifications/View/IncidentContactRenderer.php
+++ b/library/Notifications/View/IncidentContactRenderer.php
@@ -4,11 +4,13 @@
namespace Icinga\Module\Notifications\View;
+use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Common\Icons;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Model\IncidentContact;
use ipl\Html\Attributes;
use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Web\Common\ItemRenderer;
@@ -20,6 +22,23 @@ class IncidentContactRenderer implements ItemRenderer
{
use Translation;
+ /** @var bool Whether the rendered item should not include a link to the contact */
+ private bool $disableContactLink = false;
+
+ /**
+ * Set whether the rendered item should not include a link to the contact
+ *
+ * @param bool $disableLink
+ *
+ * @return $this
+ */
+ public function disableContactLink(bool $disableLink): static
+ {
+ $this->disableContactLink = $disableLink;
+
+ return $this;
+ }
+
public function assembleAttributes($item, Attributes $attributes, string $layout): void
{
$attributes->get('class')->addValue('incident-contact');
@@ -32,7 +51,15 @@ public function assembleVisual($item, HtmlDocument $visual, string $layout): voi
public function assembleTitle($item, HtmlDocument $title, string $layout): void
{
- $title->addHtml(new Link($item->full_name, Links::contact($item->id), ['class' => 'subject']));
+ if (! $this->disableContactLink) {
+ $title->addHtml(new Link($item->full_name, Links::contact($item->id), ['class' => 'subject']));
+ } else {
+ $title->addHtml(new HtmlElement(
+ 'span',
+ Attributes::create(['class' => 'subject']),
+ Text::create($item->full_name)
+ ));
+ }
if ($item->role === 'manager') {
$title->addHtml(new Text($this->translate('manages this incident')));
diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php
index 57814a945..944e7d5a8 100644
--- a/library/Notifications/Widget/Detail/IncidentDetail.php
+++ b/library/Notifications/Widget/Detail/IncidentDetail.php
@@ -7,6 +7,7 @@
use ArrayIterator;
use Icinga\Module\Icingadb\Model\CustomvarFlat;
use Icinga\Module\Icingadb\Widget\Detail\CustomVarTable;
+use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Hook\ObjectsRendererHook;
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
use Icinga\Module\Notifications\Model\Incident;
@@ -26,6 +27,7 @@
class IncidentDetail extends BaseHtmlElement
{
+ use Auth;
use Translation;
/** @var Incident */
@@ -58,10 +60,14 @@ protected function createContacts()
$contacts[] = $contact;
}
+ $disableContactLink = ! $this->getAuth()->hasPermission('notifications/view/contacts')
+ || ! $this->getAuth()->hasPermission('notifications/config/contacts');
+
return [
Html::tag('h2', t('Subscribers')),
- (new ObjectList($contacts, new IncidentContactRenderer()))
+ (new ObjectList($contacts, (new IncidentContactRenderer())->disableContactLink($disableContactLink)))
->setItemLayoutClass(MinimalItemLayout::class)
+ ->setDetailActionsDisabled($disableContactLink)
];
}
diff --git a/library/Notifications/Widget/ItemList/ContactGroupListItem.php b/library/Notifications/Widget/ItemList/ContactGroupListItem.php
deleted file mode 100644
index 3c6a0a24d..000000000
--- a/library/Notifications/Widget/ItemList/ContactGroupListItem.php
+++ /dev/null
@@ -1,52 +0,0 @@
-getAttributes()->set('data-action-item', true);
- }
-
- protected function assembleVisual(BaseHtmlElement $visual): void
- {
- $visual->addHtml(new HtmlElement(
- 'div',
- Attributes::create(['class' => 'contact-ball']),
- Text::create(grapheme_substr($this->item->name, 0, 1))
- ));
- }
-
- protected function assembleMain(BaseHtmlElement $main): void
- {
- $main->addHtml($this->createHeader());
- }
-
- protected function assembleHeader(BaseHtmlElement $header): void
- {
- $header->addHtml($this->createTitle());
- }
-
- protected function assembleTitle(BaseHtmlElement $title): void
- {
- $title->addHtml(new Link($this->item->name, Links::contactGroup($this->item->id), ['class' => 'subject']));
- }
-}
diff --git a/library/Notifications/Widget/ItemList/ContactListItem.php b/library/Notifications/Widget/ItemList/ContactListItem.php
deleted file mode 100644
index fb2da107d..000000000
--- a/library/Notifications/Widget/ItemList/ContactListItem.php
+++ /dev/null
@@ -1,62 +0,0 @@
-getAttributes()
- ->set('data-action-item', true);
- }
-
- protected function assembleVisual(BaseHtmlElement $visual): void
- {
- $visual->addHtml(new HtmlElement(
- 'div',
- Attributes::create(['class' => 'contact-ball']),
- Text::create(grapheme_substr($this->item->full_name, 0, 1))
- ));
- }
-
- protected function assembleTitle(BaseHtmlElement $title): void
- {
- $title->addHtml(new Link(
- $this->item->full_name,
- Url::fromPath('notifications/contact', ['id' => $this->item->id]),
- ['class' => 'subject']
- ));
- }
-
- protected function assembleHeader(BaseHtmlElement $header): void
- {
- $header->add($this->createTitle());
- }
-
- protected function assembleMain(BaseHtmlElement $main): void
- {
- $main->add($this->createHeader());
-
- $main->add($this->createFooter());
- }
-}