Skip to content

Commit 01bf7ec

Browse files
committed
Create handler for unauthorized requests
Signed-off-by: Tim Goudriaan <tim@codedmonkey.com>
1 parent 2c9bc36 commit 01bf7ec

10 files changed

Lines changed: 200 additions & 0 deletions

config/packages/security.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ security:
3838
auth_form_path: mfa_login
3939
check_path: mfa_login_check
4040

41+
# Custom access denied handler
42+
access_denied_handler: CodedMonkey\Dirigent\Security\AccessDeniedHandler
43+
4144
# activate different ways to authenticate
4245
# https://symfony.com/doc/current/security.html#the-firewall
4346

src/Controller/Dashboard/DashboardCredentialsController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
1111
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
1212
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
13+
use Symfony\Component\Security\Http\Attribute\IsGranted;
1314

1415
#[AdminRoute(path: '/credentials', name: 'credentials')]
16+
#[IsGranted('ROLE_ADMIN')]
1517
class DashboardCredentialsController extends AbstractCrudController
1618
{
1719
public static function getEntityFqcn(): string

src/Controller/Dashboard/DashboardRegistryController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
2020
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
2121
use Symfony\Component\HttpFoundation\RedirectResponse;
22+
use Symfony\Component\Security\Http\Attribute\IsGranted;
2223

2324
#[AdminRoute(path: '/registries', name: 'registries')]
25+
#[IsGranted('ROLE_ADMIN')]
2426
class DashboardRegistryController extends AbstractCrudController
2527
{
2628
public static function getEntityFqcn(): string

src/Controller/Dashboard/DashboardUserController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
1616
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
1717
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
18+
use Symfony\Component\Security\Http\Attribute\IsGranted;
1819

1920
#[AdminRoute(path: '/users', name: 'users')]
21+
#[IsGranted('ROLE_ADMIN')]
2022
class DashboardUserController extends AbstractCrudController
2123
{
2224
public static function getEntityFqcn(): string
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodedMonkey\Dirigent\Security;
6+
7+
use Symfony\Component\HttpFoundation\RedirectResponse;
8+
use Symfony\Component\HttpFoundation\Request;
9+
use Symfony\Component\HttpFoundation\Response;
10+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
11+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
12+
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
13+
use Twig\Environment;
14+
15+
readonly class AccessDeniedHandler implements AccessDeniedHandlerInterface
16+
{
17+
public function __construct(
18+
private Environment $twig,
19+
private UrlGeneratorInterface $urlGenerator,
20+
) {
21+
}
22+
23+
public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
24+
{
25+
if ($request->isXmlHttpRequest()) {
26+
return new Response(
27+
json_encode([
28+
'error' => 'Access denied',
29+
'message' => 'You do not have permission to access this resource.',
30+
]),
31+
Response::HTTP_FORBIDDEN,
32+
['Content-Type' => 'application/json'],
33+
);
34+
}
35+
36+
// If user is not authenticated, redirect to login
37+
if (!$request->getSession()->has('_security_main')) {
38+
return new RedirectResponse(
39+
$this->urlGenerator->generate('dashboard_login', [
40+
'_target_path' => $request->getUri(),
41+
])
42+
);
43+
}
44+
45+
// Show access denied error page
46+
$content = $this->twig->render('dashboard/errors/access_denied.html.twig', [
47+
'exception' => $accessDeniedException,
48+
]);
49+
50+
return new Response($content, Response::HTTP_FORBIDDEN);
51+
}
52+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% extends '@EasyAdmin/page/content.html.twig' %}
2+
3+
{% block page_title %}Access Denied{% endblock %}
4+
5+
{% block page_content %}
6+
<p class="text-muted">You do not have permission to access this page.</p>
7+
{% endblock %}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Controller\Dashboard;
4+
5+
use CodedMonkey\Dirigent\Tests\Helper\WebTestCaseTrait;
6+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class DashboardCredentialsControllerTest extends WebTestCase
10+
{
11+
use WebTestCaseTrait;
12+
13+
public function testUserDoesNotHaveAccess(): void
14+
{
15+
$client = static::createClient();
16+
$this->loginUser();
17+
18+
$client->request('GET', '/credentials');
19+
20+
$this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
21+
}
22+
23+
public function testAdministratorHasAccess(): void
24+
{
25+
$client = static::createClient();
26+
$this->loginUser('admin');
27+
28+
$client->request('GET', '/credentials');
29+
30+
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Controller\Dashboard;
4+
5+
use CodedMonkey\Dirigent\Tests\Helper\WebTestCaseTrait;
6+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class DashboardRegistryControllerTest extends WebTestCase
10+
{
11+
use WebTestCaseTrait;
12+
13+
public function testUserDoesNotHaveAccess(): void
14+
{
15+
$client = static::createClient();
16+
$this->loginUser();
17+
18+
$client->request('GET', '/registries');
19+
20+
$this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
21+
}
22+
23+
public function testAdministratorHasAccess(): void
24+
{
25+
$client = static::createClient();
26+
$this->loginUser('admin');
27+
28+
$client->request('GET', '/registries');
29+
30+
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
31+
}
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Controller\Dashboard;
4+
5+
use CodedMonkey\Dirigent\Tests\Helper\WebTestCaseTrait;
6+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class DashboardRootControllerPermissionsTest extends WebTestCase
10+
{
11+
use WebTestCaseTrait;
12+
13+
public function testAdministratorHasAdminMenu(): void
14+
{
15+
$client = static::createClient();
16+
$this->loginUser('admin');
17+
18+
$client->request('GET', '/');
19+
20+
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
21+
22+
$this->assertAnySelectorTextSame('.menu-item-label', 'Administration');
23+
}
24+
25+
public function testUserDoesNotHaveAdminMenu(): void
26+
{
27+
$client = static::createClient();
28+
$this->loginUser();
29+
30+
$client->request('GET', '/');
31+
32+
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
33+
34+
$this->assertAnySelectorTextNotContains('.menu-item-label', 'Administration');
35+
}
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Controller\Dashboard;
4+
5+
use CodedMonkey\Dirigent\Tests\Helper\WebTestCaseTrait;
6+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class DashboardUserControllerTest extends WebTestCase
10+
{
11+
use WebTestCaseTrait;
12+
13+
public function testUserDoesNotHaveAccess(): void
14+
{
15+
$client = static::createClient();
16+
$this->loginUser();
17+
18+
$client->request('GET', '/users');
19+
20+
$this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN);
21+
}
22+
23+
public function testAdministratorHasAccess(): void
24+
{
25+
$client = static::createClient();
26+
$this->loginUser('admin');
27+
28+
$client->request('GET', '/users');
29+
30+
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
31+
}
32+
}

0 commit comments

Comments
 (0)