Check permissions for time travel and element undo.

This commit is contained in:
Jan Böhmer 2020-03-07 20:49:52 +01:00
parent 254d4e6c69
commit 8a61b465d0
23 changed files with 370 additions and 90 deletions

View file

@ -320,6 +320,10 @@ showing the sidebar (on devices with md or higher)
}
}
.not-allowed {
cursor: not-allowed !important;
}
/**************************************
btn-xs
btn-xs

View file

@ -67,6 +67,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
show_history:
label: "perm.part.show_history"
bit: 30
revert_element:
label: "perm.revert_elements"
bit: 32
alsoSet: ["read", "edit", "create", "delete", "show_history"]
parts_name: &PART_ATTRIBUTE # We define a template here, that we can use for all part attributes.
label: "perm.part.name"
@ -154,8 +158,31 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
label: "perm.part.lots"
parts_attachments:
<<: *PART_MULTI_ATTRIBUTE
group: "structures"
label: "perm.part.attachments"
operations:
read:
label: "perm.read"
bit: 0
edit:
label: "perm.edit"
bit: 2
alsoSet: 'read'
create:
label: "perm.create"
bit: 4
alsoSet: ['read', 'edit']
delete:
label: "perm.delete"
bit: 6
alsoSet: ['read']
show_history:
label: "perm.show_history"
bit: 8
revert_element:
label: "perm.revert_elements"
bit: 10
alsoSet: ["read", "edit", "create", "delete", "show_history"]
parts_order:
<<: *PART_ATTRIBUTE
@ -189,6 +216,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
show_users:
label: "perm.show_users"
bit: 12
show_history:
label: "perm.show_history"
bit: 14
revert_element:
label: "perm.revert_elements"
bit: 16
alsoSet: ["read", "edit", "create", "delete", "show_history"]
footprints:
<<: *PART_CONTAINING
@ -243,6 +277,12 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
statistics:
label: "perm.tools.statistics"
bit: 10
lastActivity:
label: "perm.tools.lastActivity"
bit: 12
timetravel:
label: "perm.tools.timeTravel"
bit: 14
groups:
label: "perm.groups"
@ -270,6 +310,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
label: "perm.edit_permissions"
alsoSet: ['read', 'edit']
bit: 10
show_history:
label: "perm.show_history"
bit: 12
revert_element:
label: "perm.revert_elements"
bit: 14
alsoSet: ["read", "edit", "create", "delete", "move", "edit_permissions", "show_history"]
users:
label: "perm.users"
@ -309,6 +356,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
change_user_settings:
label: "perm.users.change_user_settings"
bit: 16
show_history:
label: "perm.show_history"
bit: 18
revert_element:
label: "perm.revert_elements"
bit: 20
alsoSet: ["read", "edit", "create", "delete", "move", "edit_permissions", "show_history", "edit_infos", "change_group", "edit_username"]
database:
label: "perm.database"

View file

@ -89,9 +89,9 @@ abstract class BaseAdminController extends AbstractController
protected $dataTableFactory;
public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler,
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
DataTableFactory $dataTableFactory)
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler,
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
DataTableFactory $dataTableFactory)
{
if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
@ -119,6 +119,8 @@ abstract class BaseAdminController extends AbstractController
$timeTravel_timestamp = null;
if ($timestamp !== null) {
$this->denyAccessUnlessGranted('@tools.timeTravel');
$this->denyAccessUnlessGranted('show_history', $part);
//If the timestamp only contains numbers interpret it as unix timestamp
if (ctype_digit($timestamp)) {
$timeTravel_timestamp = new \DateTime();
@ -129,14 +131,22 @@ abstract class BaseAdminController extends AbstractController
$this->timeTravel->revertEntityToTimestamp($entity, $timeTravel_timestamp);
}
$table = $this->dataTableFactory->createFromType(LogDataTable::class, [
'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
'mode' => 'element_history'
], ['pageLength' => 10])
->handleRequest($request);
if ($this->isGranted('show_history', $entity) ) {
$table = $this->dataTableFactory->createFromType(
LogDataTable::class,
[
'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
'mode' => 'element_history'
],
['pageLength' => 10]
)
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
if ($table->isCallback()) {
return $table->getResponse();
}
} else {
$table = null;
}
$form = $this->createForm($this->form_class, $entity, [

View file

@ -86,13 +86,21 @@ class HomepageController extends AbstractController
*/
public function homepage(Request $request, GitVersionInfo $versionInfo): Response
{
$table = $this->dataTable->createFromType(LogDataTable::class, [
'mode' => 'last_activity'
], ['pageLength' => 10])
->handleRequest($request);
if ($this->isGranted("@tools.lastActivity")) {
$table = $this->dataTable->createFromType(
LogDataTable::class,
[
'mode' => 'last_activity'
],
['pageLength' => 10]
)
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
if ($table->isCallback()) {
return $table->getResponse();
}
} else {
$table = null;
}
return $this->render('homepage.html.twig', [

View file

@ -121,6 +121,8 @@ class LogController extends AbstractController
throw new \InvalidArgumentException('No log entry with the given ID is existing!');
}
$this->denyAccessUnlessGranted('revert_element', $log_element->getTargetClass());
$eventUndoHelper->setMode($mode);
$eventUndoHelper->setUndoneEvent($log_element);

View file

@ -98,6 +98,8 @@ class PartController extends AbstractController
$timeTravel_timestamp = null;
if ($timestamp !== null) {
$this->denyAccessUnlessGranted('@tools.timeTravel');
$this->denyAccessUnlessGranted('show_history', $part);
//If the timestamp only contains numbers interpret it as unix timestamp
if (ctype_digit($timestamp)) {
$timeTravel_timestamp = new \DateTime();
@ -108,14 +110,18 @@ class PartController extends AbstractController
$timeTravel->revertEntityToTimestamp($part, $timeTravel_timestamp);
}
$table = $dataTable->createFromType(LogDataTable::class, [
'filter_elements' => $historyHelper->getAssociatedElements($part),
'mode' => 'element_history'
], ['pageLength' => 10])
->handleRequest($request);
if ($this->isGranted('show_history', $part) ) {
$table = $dataTable->createFromType(LogDataTable::class, [
'filter_elements' => $historyHelper->getAssociatedElements($part),
'mode' => 'element_history'
], ['pageLength' => 10])
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
if ($table->isCallback()) {
return $table->getResponse();
}
} else {
$table = null;
}
return $this->render(

View file

@ -42,11 +42,13 @@ class IconLinkColumn extends AbstractColumn
'icon' => 'fas fa-fw fa-edit',
'title' => null,
'href' => null,
'disabled' => false,
]);
$resolver->setAllowedTypes('title', ['null', 'string', 'callable']);
$resolver->setAllowedTypes('icon', ['null', 'string', 'callable']);
$resolver->setAllowedTypes('href', ['null', 'string', 'callable']);
$resolver->setAllowedTypes('disabled', ['bool', 'callable']);
return $this;
}
@ -56,10 +58,12 @@ class IconLinkColumn extends AbstractColumn
$href = $this->getHref($value, $context);
$icon = $this->getIcon($value, $context);
$title = $this->getTitle($value, $context);
$disabled = $this->getDisabled($value, $context);
if ($href !== null) {
return sprintf(
'<a class="btn btn-primary btn-sm" href="%s" title="%s"><i class="%s"></i></a>',
'<a class="btn btn-primary btn-sm %s" href="%s" title="%s"><i class="%s"></i></a>',
$disabled ? 'disabled' : '',
$href,
$title,
$icon
@ -69,6 +73,18 @@ class IconLinkColumn extends AbstractColumn
return "";
}
protected function getDisabled($value, $context): bool
{
$provider = $this->options['disabled'];
if (is_bool($provider)) {
return $provider;
}
if (is_callable($provider)) {
return call_user_func($provider, $value, $context);
}
return false;
}
protected function getHref($value, $context): ?string
{
$provider = $this->options['href'];

View file

@ -27,15 +27,18 @@ use App\Entity\LogSystem\ElementCreatedLogEntry;
use App\Entity\LogSystem\ElementDeletedLogEntry;
use App\Entity\LogSystem\ElementEditedLogEntry;
use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class RevertLogColumn extends AbstractColumn
{
protected $translator;
protected $security;
public function __construct(TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator, Security $security)
{
$this->translator = $translator;
$this->security = $security;
}
/**
@ -65,17 +68,21 @@ class RevertLogColumn extends AbstractColumn
return '';
}
$disabled = !$this->security->isGranted('revert_element', $context->getTargetClass());
$tmp = '<div class="btn-group btn-group-sm">';
$tmp .= sprintf(
'<button type="submit" class="btn btn-outline-secondary" name="undo" value="%d"><i class="fas fa-fw %s" title="%s"></i></button>',
'<button type="submit" class="btn btn-outline-secondary" name="undo" value="%d" %s><i class="fas fa-fw %s" title="%s"></i></button>',
$context->getID(),
$disabled ? 'disabled' : '',
$icon,
$title
);
$tmp .= sprintf(
'<button type="submit" class="btn btn-outline-secondary" name="revert" value="%d"><i class="fas fa-fw fa-backward" title="%s"></i></button>',
'<button type="submit" class="btn btn-outline-secondary" name="revert" value="%d" %s><i class="fas fa-fw fa-backward" title="%s"></i></button>',
$context->getID(),
$disabled ? 'disabled' : '',
$this->translator->trans('log.undo.revert')
);

View file

@ -66,6 +66,7 @@ use Omines\DataTablesBundle\DataTableTypeInterface;
use Psr\Log\LogLevel;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Flex\Options;
@ -76,15 +77,17 @@ class LogDataTable implements DataTableTypeInterface
protected $urlGenerator;
protected $entityURLGenerator;
protected $logRepo;
protected $security;
public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator,
UrlGeneratorInterface $urlGenerator, EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager)
UrlGeneratorInterface $urlGenerator, EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager, Security $security)
{
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
$this->translator = $translator;
$this->urlGenerator = $urlGenerator;
$this->entityURLGenerator = $entityURLGenerator;
$this->logRepo = $entityManager->getRepository(AbstractLogEntry::class);
$this->security = $security;
}
public function configureOptions(OptionsResolver $optionsResolver)
@ -235,7 +238,13 @@ class LogDataTable implements DataTableTypeInterface
}
}
return null;
},
'disabled' => function ($value, AbstractLogEntry $context) {
return
!$this->security->isGranted('@tools.timetravel')
|| !$this->security->isGranted('show_history', $context->getTargetClass());
}
]);
$dataTable->add('actionRevert', RevertLogColumn::class, [

View file

@ -481,7 +481,7 @@ class PermissionsEmbed
*/
final protected static function readBitPair($data, int $n): int
{
Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
//Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
if (0 !== $n % 2) {
throw new InvalidArgumentException('$n must be dividable by 2, because we address bit pairs here!');
}
@ -501,7 +501,7 @@ class PermissionsEmbed
*/
final protected static function writeBitPair(int $data, int $n, int $new): int
{
Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
//Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
Assert::lessThanEq($new, 3, '$new must be smaller than 3, because a bit pair is written! Got %s.');
Assert::greaterThanEq($new, 0, '$new must not be negative, because a bit pair is written! Got %s.');

View file

@ -58,11 +58,7 @@ class AttachmentVoter extends ExtendedVoter
*/
protected function voteOnUser($attribute, $subject, User $user): bool
{
if ($subject instanceof Attachment) {
return $this->resolver->inherit($user, 'parts_attachments', $attribute) ?? false;
}
return false;
return $this->resolver->inherit($user, 'parts_attachments', $attribute) ?? false;
}
/**
@ -75,10 +71,11 @@ class AttachmentVoter extends ExtendedVoter
*/
protected function supports($attribute, $subject)
{
if ($subject instanceof Attachment) {
if (is_a($subject, Attachment::class, true)) {
return in_array($attribute, $this->resolver->listOperationsForPermission('parts_attachments'), false);
}
//Allow class name as subject
return false;
}
}

View file

@ -57,11 +57,7 @@ class GroupVoter extends ExtendedVoter
*/
protected function voteOnUser($attribute, $subject, User $user): bool
{
if ($subject instanceof Group) {
return $this->resolver->inherit($user, 'groups', $attribute) ?? false;
}
return false;
return $this->resolver->inherit($user, 'groups', $attribute) ?? false;
}
/**
@ -74,7 +70,7 @@ class GroupVoter extends ExtendedVoter
*/
protected function supports($attribute, $subject)
{
if ($subject instanceof Group) {
if (is_a($subject, Group::class, true)) {
return $this->resolver->isValidOperation('groups', $attribute);
}

View file

@ -51,25 +51,21 @@ class LogEntryVoter extends ExtendedVoter
protected function voteOnUser($attribute, $subject, User $user): bool
{
if ($subject instanceof AbstractLogEntry) {
if ('delete' === $attribute) {
return $this->resolver->inherit($user, 'system', 'delete_logs') ?? false;
}
if ('read' === $attribute) {
//Allow read of the users own log entries
if (
$subject->getUser() === $user
&& $this->resolver->inherit($user, 'self', 'show_logs')
) {
return true;
}
return $this->resolver->inherit($user, 'system', 'show_logs') ?? false;
}
if ('delete' === $attribute) {
return $this->resolver->inherit($user, 'system', 'delete_logs') ?? false;
}
return false;
if ('read' === $attribute) {
//Allow read of the users own log entries
if (
$subject->getUser() === $user
&& $this->resolver->inherit($user, 'self', 'show_logs')
) {
return true;
}
return $this->resolver->inherit($user, 'system', 'show_logs') ?? false;
}
}
protected function supports($attribute, $subject)

View file

@ -0,0 +1,57 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Security\Voter;
use App\Entity\Parts\PartLot;
use App\Entity\PriceInformations\Orderdetail;
use App\Entity\UserSystem\User;
class OrderdetailVoter extends ExtendedVoter
{
/** @var string[] When this permsission are encountered, they are checked on part */
protected const PART_PERMS = ['show_history', 'revert_element'];
/**
* @inheritDoc
*/
protected function voteOnUser($attribute, $subject, User $user): bool
{
if (in_array($attribute, self::PART_PERMS, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
}
return $this->resolver->inherit($user, 'parts_orderdetails', $attribute) ?? false;
}
/**
* @inheritDoc
*/
protected function supports($attribute, $subject)
{
if (is_a($subject, Orderdetail::class, true)) {
return in_array($attribute, array_merge(
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_orderdetails')
), true);
}
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Security\Voter;
use App\Entity\Parts\PartLot;
use App\Entity\UserSystem\User;
class PartLotVoter extends ExtendedVoter
{
/** @var string[] When this permsission are encountered, they are checked on part */
protected const PART_PERMS = ['show_history', 'revert_element'];
/**
* @inheritDoc
*/
protected function voteOnUser($attribute, $subject, User $user): bool
{
if (in_array($attribute, self::PART_PERMS, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
}
return $this->resolver->inherit($user, 'parts_lots', $attribute) ?? false;
}
/**
* @inheritDoc
*/
protected function supports($attribute, $subject)
{
if (is_a($subject, PartLot::class, true)) {
return in_array($attribute, array_merge(
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_lots')
), true);
}
}
}

View file

@ -57,11 +57,7 @@ class PartVoter extends ExtendedVoter
protected function supports($attribute, $subject)
{
// replace with your own logic
// https://symfony.com/doc/current/security/voters.html
//return ($subject instanceof Part || in_array($subject, ['PERM_parts', 'PERM_parts_name']));
if ($subject instanceof Part) {
if (is_a($subject, Part::class, true)) {
//Check if a sub permission should be checked -> $attribute has format name.edit
if (false !== strpos($attribute, '.')) {
[$perm, $op] = explode('.', $attribute);
@ -72,24 +68,21 @@ class PartVoter extends ExtendedVoter
return $this->resolver->isValidOperation('parts', $attribute);
}
//Allow class name as subject
return false;
}
protected function voteOnUser($attribute, $subject, User $user): bool
{
if ($subject instanceof Part) {
//Check for sub permissions
if (false !== strpos($attribute, '.')) {
[$perm, $op] = explode('.', $attribute);
//Check for sub permissions
if (false !== strpos($attribute, '.')) {
[$perm, $op] = explode('.', $attribute);
return $this->resolver->inherit($user, 'parts_'.$perm, $op) ?? false;
}
//Null concealing operator means, that no
return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
return $this->resolver->inherit($user, 'parts_'.$perm, $op) ?? false;
}
//Deny access by default.
return false;
//Null concealing operator means, that no
return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Security\Voter;
use App\Entity\Parts\PartLot;
use App\Entity\PriceInformations\Pricedetail;
use App\Entity\UserSystem\User;
class PricedetailVoter extends ExtendedVoter
{
/** @var string[] When this permsission are encountered, they are checked on part */
protected const PART_PERMS = ['show_history', 'revert_element'];
/**
* @inheritDoc
*/
protected function voteOnUser($attribute, $subject, User $user): bool
{
if (in_array($attribute, self::PART_PERMS, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
}
return $this->resolver->inherit($user, 'parts_prices', $attribute) ?? false;
}
/**
* @inheritDoc
*/
protected function supports($attribute, $subject)
{
if (is_a($subject, Pricedetail::class, true)) {
return in_array($attribute, array_merge(
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_prices')
), true);
}
}
}

View file

@ -43,11 +43,13 @@ declare(strict_types=1);
namespace App\Security\Voter;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Devices\Device;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
@ -67,24 +69,29 @@ class StructureVoter extends ExtendedVoter
*/
protected function supports($attribute, $subject)
{
if (is_object($subject)) {
if (is_object($subject) || is_string($subject)) {
$permission_name = $this->instanceToPermissionName($subject);
//If permission name is null, then the subject is not supported
return (null !== $permission_name) && $this->resolver->isValidOperation($permission_name, $attribute);
}
return false;
}
/**
* Maps a instance type to the permission name.
*
* @param mixed $subject The subject for which the permission name should be generated
* @param object|string $subject The subject for which the permission name should be generated
*
* @return string|null the name of the permission for the subject's type or null, if the subject is not supported
*/
protected function instanceToPermissionName($subject): ?string
{
$class_name = get_class($subject);
if (!is_string($subject)) {
$class_name = get_class($subject);
} else {
$class_name = $subject;
}
switch ($class_name) {
case AttachmentType::class:
return 'attachment_types';

View file

@ -57,11 +57,11 @@ class UserVoter extends ExtendedVoter
*/
protected function supports($attribute, $subject)
{
if ($subject instanceof User) {
if (is_a($subject, User::class, true)) {
return in_array($attribute, array_merge(
$this->resolver->listOperationsForPermission('users'),
$this->resolver->listOperationsForPermission('self')),
false
false
);
}
@ -89,10 +89,11 @@ class UserVoter extends ExtendedVoter
return $tmp;
}
}
//Else just check users permission:
if ($this->resolver->isValidOperation('users', $attribute)) {
return $this->resolver->inherit($user, 'users', $attribute) ?? false;
}
}
//Else just check users permission:
if ($this->resolver->isValidOperation('users', $attribute)) {
return $this->resolver->inherit($user, 'users', $attribute) ?? false;
}
return false;

View file

@ -59,8 +59,8 @@
<ul class="nav nav-tabs mt-2">
<li class="nav-item"><a class="link-anchor active nav-link" data-toggle="tab" href="#home">{% trans %}standard.label{% endtrans %}</a></li>
<li class="nav-item"><a data-toggle="tab" class="link-anchor nav-link" href="#info">{% trans %}infos.label{% endtrans %}</a></li>
{% if datatable is defined and datatable is not null %}
<li class="nav-item"><a data-toggle="tab" class="link-anchor nav-link" href="#history">{% trans %}history.label{% endtrans %}</a></li>
{% if datatable is defined %}
<li class="nav-item {% if datatable is null %}not-allowed{% endif %}"><a data-toggle="tab" class="link-anchor nav-link {% if datatable is null %}disabled{% endif %}" href="#history">{% trans %}history.label{% endtrans %}</a></li>
{% endif %}
{% if entity.id %}
<li class="nav-item"><a data-toggle="tab" class="link-anchor nav-link" href="#export">{% trans %}export.label{% endtrans %}</a> </li>

View file

@ -1,3 +1,5 @@
<div class="mt-2">
{% include "LogSystem/_log_table.html.twig" %}
{% if datatable is not null %}
{% include "LogSystem/_log_table.html.twig" %}
{% endif %}
</div>

View file

@ -72,8 +72,8 @@
<span class="badge badge-secondary">{{ part.orderdetails | length }}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="history-tab" data-toggle="tab" href="#history" role="tab">
<li class="nav-item {% if datatable is null %}not-allowed{% endif %}">
<a class="nav-link {% if datatable is null %}disabled{% endif %}" id="history-tab" data-toggle="tab" href="#history" role="tab">
<i class="fas fa-history"></i>
{% trans %}vendor.partinfo.history{% endtrans %}
</a>

View file

@ -40,10 +40,12 @@
</div>
</div>
{% if datatable is not null %}
<div class="card mt-3">
<div class="card-header"><i class="fas fa-fw fa-history"></i> {% trans %}homepage.last_activity{% endtrans %}</div>
<div class="card-body">
{% include "LogSystem/_log_table.html.twig" %}
</div>
</div>
{% endif %}
{% endblock %}