feat: gate functionality for customer expert users
This commit is contained in:
@@ -17,8 +17,12 @@ security:
|
|||||||
roles: [ ROLE_MAILJET_WEBHOOK ]
|
roles: [ ROLE_MAILJET_WEBHOOK ]
|
||||||
|
|
||||||
role_hierarchy:
|
role_hierarchy:
|
||||||
|
# ROLE_CUSTOMER_EXPERT gates the booking-draft and log surfaces, which used to be
|
||||||
|
# ROLE_ADMIN only. Inheriting it here is what keeps those surfaces open to administrators
|
||||||
|
# after the #[IsGranted] attributes moved to the narrower role.
|
||||||
ROLE_ADMIN:
|
ROLE_ADMIN:
|
||||||
- ROLE_GROUPS_ADMIN
|
- ROLE_GROUPS_ADMIN
|
||||||
|
- ROLE_CUSTOMER_EXPERT
|
||||||
ROLE_GROUPS_ADMIN:
|
ROLE_GROUPS_ADMIN:
|
||||||
- ROLE_GROUPS_MANAGER
|
- ROLE_GROUPS_MANAGER
|
||||||
firewalls:
|
firewalls:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class DeleteController extends AbstractController
|
class DeleteController extends AbstractController
|
||||||
{
|
{
|
||||||
use ReturnUrlTrait;
|
use ReturnUrlTrait;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class ExportController extends AbstractController
|
class ExportController extends AbstractController
|
||||||
{
|
{
|
||||||
use ReturnUrlTrait;
|
use ReturnUrlTrait;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class FilterController extends AbstractController
|
class FilterController extends AbstractController
|
||||||
{
|
{
|
||||||
use ListFilterTrait;
|
use ListFilterTrait;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class IndexController extends AbstractController
|
class IndexController extends AbstractController
|
||||||
{
|
{
|
||||||
use ListFilterTrait;
|
use ListFilterTrait;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class ShowController extends AbstractController
|
class ShowController extends AbstractController
|
||||||
{
|
{
|
||||||
use ReturnUrlTrait;
|
use ReturnUrlTrait;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class DownloadController extends AbstractController
|
class DownloadController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly XmlDumpReader $xmlDumpReader)
|
public function __construct(private readonly XmlDumpReader $xmlDumpReader)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class FilterController extends AbstractController
|
class FilterController extends AbstractController
|
||||||
{
|
{
|
||||||
use ListFilterTrait;
|
use ListFilterTrait;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class IndexController extends AbstractController
|
class IndexController extends AbstractController
|
||||||
{
|
{
|
||||||
use ListFilterTrait;
|
use ListFilterTrait;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
|
||||||
class XmlDumpController extends AbstractController
|
class XmlDumpController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly XmlDumpReader $xmlDumpReader)
|
public function __construct(private readonly XmlDumpReader $xmlDumpReader)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class RecentLogEntriesWidgetProvider implements DashboardWidgetProviderInterface
|
|||||||
|
|
||||||
public function getRequiredRole(): string
|
public function getRequiredRole(): string
|
||||||
{
|
{
|
||||||
return Role::ADMIN;
|
return Role::CUSTOMER_EXPERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPriority(): int
|
public function getPriority(): int
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Menu;
|
namespace App\Menu;
|
||||||
|
|
||||||
|
use App\Security\Voter\AdministrativeAccessVoter;
|
||||||
use Knp\Menu\FactoryInterface;
|
use Knp\Menu\FactoryInterface;
|
||||||
use Knp\Menu\ItemInterface;
|
use Knp\Menu\ItemInterface;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
@@ -39,7 +40,7 @@ abstract class AbstractMenuBuilder
|
|||||||
|
|
||||||
protected function addAdminItem(ItemInterface $menu): void
|
protected function addAdminItem(ItemInterface $menu): void
|
||||||
{
|
{
|
||||||
if ($this->security->isGranted('ROLE_ADMIN')) {
|
if ($this->security->isGranted(AdministrativeAccessVoter::ADMINISTRATIVE_ACCESS)) {
|
||||||
$this->addDivider($menu);
|
$this->addDivider($menu);
|
||||||
$menu->addChild('zum Adminbereich', [
|
$menu->addChild('zum Adminbereich', [
|
||||||
'route' => 'app_admin_index',
|
'route' => 'app_admin_index',
|
||||||
|
|||||||
@@ -22,16 +22,18 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
|||||||
'icon' => 'chart',
|
'icon' => 'chart',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
$menu->addChild('Buchungsentwürfe', [
|
if ($this->security->isGranted('ROLE_CUSTOMER_EXPERT')) {
|
||||||
'route' => 'app_admin_bookingeditdraft',
|
$menu->addChild('Buchungsentwürfe', [
|
||||||
'linkAttributes' => [
|
'route' => 'app_admin_bookingeditdraft',
|
||||||
'title' => 'Buchungsentwürfe',
|
'linkAttributes' => [
|
||||||
],
|
'title' => 'Buchungsentwürfe',
|
||||||
'extras' => [
|
],
|
||||||
'icon' => 'edit',
|
'extras' => [
|
||||||
'routes' => [['pattern' => '/^app_admin_bookingeditdraft/']],
|
'icon' => 'edit',
|
||||||
],
|
'routes' => [['pattern' => '/^app_admin_bookingeditdraft/']],
|
||||||
]);
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->security->isGranted('ROLE_GROUPS_ADMIN')) {
|
if ($this->security->isGranted('ROLE_GROUPS_ADMIN')) {
|
||||||
$menu->addChild('Gruppenbuchungen', [
|
$menu->addChild('Gruppenbuchungen', [
|
||||||
@@ -56,25 +58,29 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$menu->addChild('Benutzer', [
|
if ($this->security->isGranted('ROLE_ADMIN')) {
|
||||||
'route' => 'app_admin_user',
|
$menu->addChild('Benutzer', [
|
||||||
'linkAttributes' => [
|
'route' => 'app_admin_user',
|
||||||
'title' => 'Benutzer',
|
'linkAttributes' => [
|
||||||
],
|
'title' => 'Benutzer',
|
||||||
'extras' => [
|
],
|
||||||
'icon' => 'users',
|
'extras' => [
|
||||||
],
|
'icon' => 'users',
|
||||||
]);
|
],
|
||||||
$menu->addChild('Logs', [
|
]);
|
||||||
'route' => 'app_admin_log',
|
}
|
||||||
'linkAttributes' => [
|
if ($this->security->isGranted('ROLE_CUSTOMER_EXPERT')) {
|
||||||
'title' => 'Logs',
|
$menu->addChild('Logs', [
|
||||||
],
|
'route' => 'app_admin_log',
|
||||||
'extras' => [
|
'linkAttributes' => [
|
||||||
'icon' => 'list',
|
'title' => 'Logs',
|
||||||
'routes' => [['pattern' => '/^app_admin_log/']],
|
],
|
||||||
],
|
'extras' => [
|
||||||
]);
|
'icon' => 'list',
|
||||||
|
'routes' => [['pattern' => '/^app_admin_log/']],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$this->addLogoutItem($menu);
|
$this->addLogoutItem($menu);
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class AdministrativeAccessVoter extends Voter
|
|||||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
||||||
{
|
{
|
||||||
return $this->accessDecisionManager->decide($token, ['ROLE_ADMIN'])
|
return $this->accessDecisionManager->decide($token, ['ROLE_ADMIN'])
|
||||||
|| $this->accessDecisionManager->decide($token, ['ROLE_GROUPS_MANAGER']);
|
|| $this->accessDecisionManager->decide($token, ['ROLE_GROUPS_MANAGER'])
|
||||||
|
|| $this->accessDecisionManager->decide($token, ['ROLE_CUSTOMER_EXPERT']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class RecentLogEntriesWidgetProviderTest extends TestCase
|
|||||||
$this->assertStringEndsWith(str_repeat('a', 60).'…', $widget->entries[0]->label);
|
$this->assertStringEndsWith(str_repeat('a', 60).'…', $widget->entries[0]->label);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAsksForFiveEntriesAndRequiresAdmin(): void
|
public function testAsksForFiveEntriesAndRequiresCustomerExpert(): void
|
||||||
{
|
{
|
||||||
$repository = $this->createMock(LogEntryRepository::class);
|
$repository = $this->createMock(LogEntryRepository::class);
|
||||||
$repository->expects($this->once())
|
$repository->expects($this->once())
|
||||||
@@ -57,7 +57,7 @@ class RecentLogEntriesWidgetProviderTest extends TestCase
|
|||||||
|
|
||||||
$provider = new RecentLogEntriesWidgetProvider($repository, $this->urlGenerator());
|
$provider = new RecentLogEntriesWidgetProvider($repository, $this->urlGenerator());
|
||||||
|
|
||||||
$this->assertSame(Role::ADMIN, $provider->getRequiredRole());
|
$this->assertSame(Role::CUSTOMER_EXPERT, $provider->getRequiredRole());
|
||||||
$this->assertNotNull($provider->build());
|
$this->assertNotNull($provider->build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user