Skip to content

Commit

Permalink
Allow restricting reports by name & author
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Jul 12, 2023
1 parent 971c7e9 commit e6eb716
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 8 deletions.
6 changes: 4 additions & 2 deletions application/controllers/ReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ public function init()

$report = Model\Report::on($this->getDb())
->with(['timeframe'])
->filter(Filter::equal('id', $reportId))
->first();
->filter(Filter::equal('id', $reportId));

$this->applyRestrictions($report);

$report = $report->first();
if ($report === null) {
$this->httpNotFound($this->translate('Report not found'));
}
Expand Down
10 changes: 6 additions & 4 deletions application/controllers/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public function indexAction()
$reports = Report::on($this->getDb())
->withColumns(['report.timeframe.name']);

$this->applyRestrictions($reports);

$sortControl = $this->createSortControl(
$reports,
[
Expand All @@ -64,16 +66,16 @@ public function indexAction()
Html::tag('td', null, $report->timeframe->name),
Html::tag('td', null, $report->ctime->format('Y-m-d H:i')),
Html::tag('td', null, $report->mtime->format('Y-m-d H:i')),
Html::tag('td', ['class' => 'icon-col'], [
new Link(
! $this->hasPermission('reporting/reports')
? null
: Html::tag('td', ['class' => 'icon-col'], new Link(
new Icon('edit'),
Url::fromPath('reporting/report/edit', ['id' => $report->id]),
[
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]
)
])
))
]);
}

Expand Down
5 changes: 5 additions & 0 deletions configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@
'reporting/timeframes',
$this->translate('Allow managing timeframes')
);

$this->provideRestriction(
'reporting/reports',
$this->translate('Restrict access to the reports that match the provided filter')
);
}
13 changes: 13 additions & 0 deletions doc/03-Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@ reporting/reports | Reports (create, edit, delete)
reporting/schedules | Schedules (create, edit, delete)
reporting/templates | Templates (create, edit, delete)
reporting/timeframes | Timeframes (create, edit, delete)

## Restrictions

Icinga Reporting currently provides a single restriction that can be used to limit users to a specific set of reports,
while having the `reporting/reports` permission.

> **Note:**
>
> Filters from multiple roles will expand the available access.
| Name | Description |
|-------------------|---------------------------------------------------------------|
| reporting/reports | Restrict access to the reports that match the provided filter |
73 changes: 73 additions & 0 deletions library/Reporting/Common/Auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Reporting\Common;

use Icinga\Authentication\Auth as IcingaAuth;
use Icinga\Exception\ConfigurationError;
use ipl\Orm\Query;
use ipl\Stdlib\Filter;
use ipl\Stdlib\Filter\Rule;
use ipl\Web\Filter\QueryString;

trait Auth
{
/**
* Apply restrictions of this module
*
* @param Query $query
*/
protected function applyRestrictions(Query $query): void
{
$auth = IcingaAuth::getInstance();
$restrictions = $auth->getRestrictions('reporting/reports');

$queryFilter = Filter::any();
foreach ($restrictions as $restriction) {
$queryFilter->add($this->parseRestriction($restriction, 'reporting/reports'));
}

$query->filter($queryFilter);
}

/**
* Parse the query string of the given restriction
*
* @param string $queryString
* @param string $restriction
* @param ?callable $onCondition
*
* @return Rule
*/
protected function parseRestriction(
string $queryString,
string $restriction,
callable $onCondition = null
): Filter\Rule {
$parser = QueryString::fromString($queryString);
if ($onCondition) {
$parser->on(QueryString::ON_CONDITION, $onCondition);
}

return $parser->on(
QueryString::ON_CONDITION,
function (Filter\Condition $condition) use ($restriction, $queryString) {
$allowedColumns = ['report.name', 'report.author'];
if (in_array($condition->getColumn(), $allowedColumns, true)) {
return;
}

throw new ConfigurationError(
t(
'Cannot apply restriction %s using the filter %s.'
. ' You can only use the following columns: %s'
),
$restriction,
$queryString,
implode(', ', $allowedColumns)
);
}
)->parse();
}
}
2 changes: 2 additions & 0 deletions library/Reporting/Web/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Icinga\Module\Reporting\Web;

use Icinga\Module\Reporting\Common\Auth;
use ipl\Web\Compat\CompatController;

class Controller extends CompatController
{
use Auth;
}
40 changes: 38 additions & 2 deletions library/Reporting/Web/Forms/ReportForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@

namespace Icinga\Module\Reporting\Web\Forms;

use Icinga\Authentication\Auth;
use Icinga\Authentication\Auth as IcingaAuth;
use Icinga\Module\Reporting\Common\Auth;
use Icinga\Module\Reporting\Database;
use Icinga\Module\Reporting\ProvidedReports;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\Form;
use ipl\Stdlib\Filter;
use ipl\Validator\CallbackValidator;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Filter\QueryString;

class ReportForm extends CompatForm
{
use Auth;
use Database;
use ProvidedReports;

Expand Down Expand Up @@ -89,6 +93,38 @@ protected function assemble()
return false;
}

$report = (object) [
'report.name' => $value,
'report.author' => IcingaAuth::getInstance()->getUser()->getUsername()
];

$onCondition = function (Filter\Condition $condition) use (&$filterNames): void {
if ($condition->getColumn() == 'report.name') {
$filterNames[] = QueryString::getRuleSymbol($condition) . $condition->getValue();
}
};

$restrictions = IcingaAuth::getInstance()->getRestrictions('reporting/reports');
$matched = false;
$filterNames = [];
foreach ($restrictions as $restriction) {
$filter = $this->parseRestriction($restriction, 'reporting/reports', $onCondition);
if (Filter::match($filter, $report)) {
$matched = true;
break;
}
}

if (! empty($restrictions) && ! $matched) {
$validator->addMessage(sprintf(
$this->translate('Please use report names that conform to this restriction: %s%s'),
'name',
implode(',', $filterNames)
));

return false;
}

return true;
}
]
Expand Down Expand Up @@ -171,7 +207,7 @@ public function onSuccess()
if ($this->id === null) {
$db->insert('report', [
'name' => $values['name'],
'author' => Auth::getInstance()->getUser()->getUsername(),
'author' => IcingaAuth::getInstance()->getUser()->getUsername(),
'timeframe_id' => $values['timeframe'],
'template_id' => $values['template'],
'ctime' => $now,
Expand Down

0 comments on commit e6eb716

Please sign in to comment.