feat: gate functionality for customer expert users

This commit is contained in:
2026-09-15 15:13:47 +02:00
parent 1d14f00bb7
commit a1d1fdde14
15 changed files with 55 additions and 43 deletions
+4
View File
@@ -17,8 +17,12 @@ security:
roles: [ ROLE_MAILJET_WEBHOOK ]
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_GROUPS_ADMIN
- ROLE_CUSTOMER_EXPERT
ROLE_GROUPS_ADMIN:
- ROLE_GROUPS_MANAGER
firewalls:
@@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class DeleteController extends AbstractController
{
use ReturnUrlTrait;
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class ExportController extends AbstractController
{
use ReturnUrlTrait;
@@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class FilterController extends AbstractController
{
use ListFilterTrait;
@@ -16,7 +16,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class IndexController extends AbstractController
{
use ListFilterTrait;
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class ShowController extends AbstractController
{
use ReturnUrlTrait;
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class DownloadController extends AbstractController
{
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\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class FilterController extends AbstractController
{
use ListFilterTrait;
+1 -1
View File
@@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class IndexController extends AbstractController
{
use ListFilterTrait;
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('ROLE_CUSTOMER_EXPERT')]
class XmlDumpController extends AbstractController
{
public function __construct(private readonly XmlDumpReader $xmlDumpReader)
@@ -37,7 +37,7 @@ class RecentLogEntriesWidgetProvider implements DashboardWidgetProviderInterface
public function getRequiredRole(): string
{
return Role::ADMIN;
return Role::CUSTOMER_EXPERT;
}
public function getPriority(): int
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Menu;
use App\Security\Voter\AdministrativeAccessVoter;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Bundle\SecurityBundle\Security;
@@ -39,7 +40,7 @@ abstract class AbstractMenuBuilder
protected function addAdminItem(ItemInterface $menu): void
{
if ($this->security->isGranted('ROLE_ADMIN')) {
if ($this->security->isGranted(AdministrativeAccessVoter::ADMINISTRATIVE_ACCESS)) {
$this->addDivider($menu);
$menu->addChild('zum Adminbereich', [
'route' => 'app_admin_index',
+35 -29
View File
@@ -22,16 +22,18 @@ class AdminMenuBuilder extends AbstractMenuBuilder
'icon' => 'chart',
],
]);
$menu->addChild('Buchungsentwürfe', [
'route' => 'app_admin_bookingeditdraft',
'linkAttributes' => [
'title' => 'Buchungsentwürfe',
],
'extras' => [
'icon' => 'edit',
'routes' => [['pattern' => '/^app_admin_bookingeditdraft/']],
],
]);
if ($this->security->isGranted('ROLE_CUSTOMER_EXPERT')) {
$menu->addChild('Buchungsentwürfe', [
'route' => 'app_admin_bookingeditdraft',
'linkAttributes' => [
'title' => 'Buchungsentwürfe',
],
'extras' => [
'icon' => 'edit',
'routes' => [['pattern' => '/^app_admin_bookingeditdraft/']],
],
]);
}
if ($this->security->isGranted('ROLE_GROUPS_ADMIN')) {
$menu->addChild('Gruppenbuchungen', [
@@ -56,25 +58,29 @@ class AdminMenuBuilder extends AbstractMenuBuilder
]);
}
$menu->addChild('Benutzer', [
'route' => 'app_admin_user',
'linkAttributes' => [
'title' => 'Benutzer',
],
'extras' => [
'icon' => 'users',
],
]);
$menu->addChild('Logs', [
'route' => 'app_admin_log',
'linkAttributes' => [
'title' => 'Logs',
],
'extras' => [
'icon' => 'list',
'routes' => [['pattern' => '/^app_admin_log/']],
],
]);
if ($this->security->isGranted('ROLE_ADMIN')) {
$menu->addChild('Benutzer', [
'route' => 'app_admin_user',
'linkAttributes' => [
'title' => 'Benutzer',
],
'extras' => [
'icon' => 'users',
],
]);
}
if ($this->security->isGranted('ROLE_CUSTOMER_EXPERT')) {
$menu->addChild('Logs', [
'route' => 'app_admin_log',
'linkAttributes' => [
'title' => 'Logs',
],
'extras' => [
'icon' => 'list',
'routes' => [['pattern' => '/^app_admin_log/']],
],
]);
}
$this->addLogoutItem($menu);
@@ -29,6 +29,7 @@ class AdministrativeAccessVoter extends Voter
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
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);
}
public function testAsksForFiveEntriesAndRequiresAdmin(): void
public function testAsksForFiveEntriesAndRequiresCustomerExpert(): void
{
$repository = $this->createMock(LogEntryRepository::class);
$repository->expects($this->once())
@@ -57,7 +57,7 @@ class RecentLogEntriesWidgetProviderTest extends TestCase
$provider = new RecentLogEntriesWidgetProvider($repository, $this->urlGenerator());
$this->assertSame(Role::ADMIN, $provider->getRequiredRole());
$this->assertSame(Role::CUSTOMER_EXPERT, $provider->getRequiredRole());
$this->assertNotNull($provider->build());
}