Improved access control for part lists.

This commit is contained in:
Jan Böhmer 2022-10-31 23:10:21 +01:00
parent d0f7949bc9
commit 3e85fc4d42
5 changed files with 68 additions and 36 deletions

View file

@ -175,6 +175,8 @@ class PartListsController extends AbstractController
*/ */
public function showCategory(Category $category, Request $request) public function showCategory(Category $category, Request $request)
{ {
$this->denyAccessUnlessGranted('@categories.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/category_list.html.twig', 'Parts/lists/category_list.html.twig',
function (PartFilter $filter) use ($category) { function (PartFilter $filter) use ($category) {
@ -195,6 +197,8 @@ class PartListsController extends AbstractController
*/ */
public function showFootprint(Footprint $footprint, Request $request) public function showFootprint(Footprint $footprint, Request $request)
{ {
$this->denyAccessUnlessGranted('@footprints.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/footprint_list.html.twig', 'Parts/lists/footprint_list.html.twig',
function (PartFilter $filter) use ($footprint) { function (PartFilter $filter) use ($footprint) {
@ -215,6 +219,8 @@ class PartListsController extends AbstractController
*/ */
public function showManufacturer(Manufacturer $manufacturer, Request $request) public function showManufacturer(Manufacturer $manufacturer, Request $request)
{ {
$this->denyAccessUnlessGranted('@manufacturers.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/manufacturer_list.html.twig', 'Parts/lists/manufacturer_list.html.twig',
function (PartFilter $filter) use ($manufacturer) { function (PartFilter $filter) use ($manufacturer) {
@ -235,6 +241,8 @@ class PartListsController extends AbstractController
*/ */
public function showStorelocation(Storelocation $storelocation, Request $request) public function showStorelocation(Storelocation $storelocation, Request $request)
{ {
$this->denyAccessUnlessGranted('@storelocations.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/store_location_list.html.twig', 'Parts/lists/store_location_list.html.twig',
function (PartFilter $filter) use ($storelocation) { function (PartFilter $filter) use ($storelocation) {
@ -255,6 +263,8 @@ class PartListsController extends AbstractController
*/ */
public function showSupplier(Supplier $supplier, Request $request) public function showSupplier(Supplier $supplier, Request $request)
{ {
$this->denyAccessUnlessGranted('@suppliers.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/supplier_list.html.twig', 'Parts/lists/supplier_list.html.twig',
function (PartFilter $filter) use ($supplier) { function (PartFilter $filter) use ($supplier) {

View file

@ -163,20 +163,29 @@ final class PartsDataTable implements DataTableTypeInterface
]) ])
->add('description', MarkdownColumn::class, [ ->add('description', MarkdownColumn::class, [
'label' => $this->translator->trans('part.table.description'), 'label' => $this->translator->trans('part.table.description'),
]) ]);
->add('category', EntityColumn::class, [
if ($this->security->isGranted('@categories.read')) {
$dataTable->add('category', EntityColumn::class, [
'label' => $this->translator->trans('part.table.category'), 'label' => $this->translator->trans('part.table.category'),
'property' => 'category', 'property' => 'category',
]) ]);
->add('footprint', EntityColumn::class, [ }
if ($this->security->isGranted('@footprints.read')) {
$dataTable->add('footprint', EntityColumn::class, [
'property' => 'footprint', 'property' => 'footprint',
'label' => $this->translator->trans('part.table.footprint'), 'label' => $this->translator->trans('part.table.footprint'),
]) ]);
->add('manufacturer', EntityColumn::class, [ }
if ($this->security->isGranted('@manufacturers.read')) {
$dataTable->add('manufacturer', EntityColumn::class, [
'property' => 'manufacturer', 'property' => 'manufacturer',
'label' => $this->translator->trans('part.table.manufacturer'), 'label' => $this->translator->trans('part.table.manufacturer'),
]) ]);
->add('storelocation', TextColumn::class, [ }
if ($this->security->isGranted('@storelocations.read')) {
$dataTable->add('storelocation', TextColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'), 'label' => $this->translator->trans('part.table.storeLocations'),
'render' => function ($value, Part $context) { 'render' => function ($value, Part $context) {
$tmp = []; $tmp = [];
@ -194,32 +203,38 @@ final class PartsDataTable implements DataTableTypeInterface
return implode('<br>', $tmp); return implode('<br>', $tmp);
}, },
]) ]);
->add('amount', TextColumn::class, [ }
'label' => $this->translator->trans('part.table.amount'),
'render' => function ($value, Part $context) {
$amount = $context->getAmountSum();
return $this->amountFormatter->format($amount, $context->getPartUnit()); $dataTable->add('amount', TextColumn::class, [
}, 'label' => $this->translator->trans('part.table.amount'),
'orderField' => 'amountSum' 'render' => function ($value, Part $context) {
]) $amount = $context->getAmountSum();
return $this->amountFormatter->format($amount, $context->getPartUnit());
},
'orderField' => 'amountSum'
])
->add('minamount', TextColumn::class, [ ->add('minamount', TextColumn::class, [
'label' => $this->translator->trans('part.table.minamount'), 'label' => $this->translator->trans('part.table.minamount'),
'visible' => false, 'visible' => false,
'render' => function ($value, Part $context) { 'render' => function ($value, Part $context) {
return $this->amountFormatter->format($value, $context->getPartUnit()); return $this->amountFormatter->format($value, $context->getPartUnit());
}, },
]) ]);
->add('partUnit', TextColumn::class, [
if ($this->security->isGranted('@footprints.read')) {
$dataTable->add('partUnit', TextColumn::class, [
'field' => 'partUnit.name', 'field' => 'partUnit.name',
'label' => $this->translator->trans('part.table.partUnit'), 'label' => $this->translator->trans('part.table.partUnit'),
'visible' => false, 'visible' => false,
]) ]);
->add('addedDate', LocaleDateTimeColumn::class, [ }
'label' => $this->translator->trans('part.table.addedDate'),
'visible' => false, $dataTable->add('addedDate', LocaleDateTimeColumn::class, [
]) 'label' => $this->translator->trans('part.table.addedDate'),
'visible' => false,
])
->add('lastModified', LocaleDateTimeColumn::class, [ ->add('lastModified', LocaleDateTimeColumn::class, [
'label' => $this->translator->trans('part.table.lastModified'), 'label' => $this->translator->trans('part.table.lastModified'),
'visible' => false, 'visible' => false,

View file

@ -80,7 +80,12 @@ class PermissionVoter extends ExtendedVoter
$attribute = ltrim($attribute, '@'); $attribute = ltrim($attribute, '@');
[$perm, $op] = explode('.', $attribute); [$perm, $op] = explode('.', $attribute);
return $this->resolver->isValidOperation($perm, $op); $valid = $this->resolver->isValidOperation($perm, $op);
//if an invalid operation is encountered, throw an exception so the developer knows it
//throw new \RuntimeException('Encountered invalid permission operation "'.$op.'" for permission "'.$perm.'"!');
return true;
} }
return false; return false;

View file

@ -76,9 +76,11 @@ final class PartsTableActionHandler
switch ($action) { switch ($action) {
case 'favorite': case 'favorite':
$this->denyAccessUnlessGranted('change_favorite', $part);
$part->setFavorite(true); $part->setFavorite(true);
break; break;
case 'unfavorite': case 'unfavorite':
$this->denyAccessUnlessGranted('change_favorite', $part);
$part->setFavorite(false); $part->setFavorite(false);
break; break;
case 'delete': case 'delete':
@ -86,19 +88,19 @@ final class PartsTableActionHandler
$this->entityManager->remove($part); $this->entityManager->remove($part);
break; break;
case 'change_category': case 'change_category':
$this->denyAccessUnlessGranted('category.edit', $part); $this->denyAccessUnlessGranted('@categories.read');
$part->setCategory($this->entityManager->find(Category::class, $target_id)); $part->setCategory($this->entityManager->find(Category::class, $target_id));
break; break;
case 'change_footprint': case 'change_footprint':
$this->denyAccessUnlessGranted('footprint.edit', $part); $this->denyAccessUnlessGranted('@footprints.read');
$part->setFootprint(null === $target_id ? null : $this->entityManager->find(Footprint::class, $target_id)); $part->setFootprint(null === $target_id ? null : $this->entityManager->find(Footprint::class, $target_id));
break; break;
case 'change_manufacturer': case 'change_manufacturer':
$this->denyAccessUnlessGranted('manufacturer.edit', $part); $this->denyAccessUnlessGranted('@manufacturers.read');
$part->setManufacturer(null === $target_id ? null : $this->entityManager->find(Manufacturer::class, $target_id)); $part->setManufacturer(null === $target_id ? null : $this->entityManager->find(Manufacturer::class, $target_id));
break; break;
case 'change_unit': case 'change_unit':
$this->denyAccessUnlessGranted('unit.edit', $part); $this->denyAccessUnlessGranted('@measurement_units.read');
$part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id)); $part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id));
break; break;

View file

@ -35,15 +35,15 @@
<select class="selectpicker" name="action" data-controller="elements--selectpicker" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }} <select class="selectpicker" name="action" data-controller="elements--selectpicker" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }}
title="{% trans %}part_list.action.action.title{% endtrans %}" required> title="{% trans %}part_list.action.action.title{% endtrans %}" required>
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}"> <optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}">
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option> <option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option>
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option> <option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option>
</optgroup> </optgroup>
<optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}"> <optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}">
<option {% if not is_granted('@parts_category.edit') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option> <option {% if not is_granted('@categories.read') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option>
<option {% if not is_granted('@parts_footprint.edit') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option> <option {% if not is_granted('@footprints.read') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option>
<option {% if not is_granted('@parts_manufacturer.edit') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option> <option {% if not is_granted('@manufacturers.read') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
<option {% if not is_granted('@parts_unit.edit') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option> <option {% if not is_granted('@measurement_units.read') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
</optgroup> </optgroup>
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option> <option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
@ -53,7 +53,7 @@
{# This is left empty, as this will be filled by Javascript #} {# This is left empty, as this will be filled by Javascript #}
</select> </select>
<button type="submit" class="btn btn-secondary">{% trans %}part_list.action.submit{% endtrans %}</button> <button type="submit" class="btn btn-secondary" {% if not is_granted('@parts.edit') %}disabled{% endif %}>{% trans %}part_list.action.submit{% endtrans %}</button>
</div> </div>
<div {{ stimulus_target('elements/datatables/parts', 'dt') }}> <div {{ stimulus_target('elements/datatables/parts', 'dt') }}>