Skip to content

Commit 14456e8

Browse files
committed
Turn the four permission-denied exceptions into a proper 403 with a human message
1 parent 4f385ef commit 14456e8

8 files changed

Lines changed: 382 additions & 34 deletions
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/**
4+
* This file is part of contao-community-alliance/dc-general.
5+
*
6+
* (c) 2013-2026 Contao Community Alliance.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* This project is provided in good faith and hope to be usable by anyone.
12+
*
13+
* @package contao-community-alliance/dc-general
14+
* @author Ingolf Steinhardt <info@e-spin.de>
15+
* @copyright 2013-2026 Contao Community Alliance.
16+
* @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later
17+
* @filesource
18+
*/
19+
20+
namespace ContaoCommunityAlliance\DcGeneral\Exception;
21+
22+
use Contao\System;
23+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
24+
use Symfony\Contracts\Translation\TranslatorInterface;
25+
26+
/**
27+
* Base class for the "you may not perform this action here" exceptions.
28+
*
29+
* These describe a state, not a programming error: the editor tried an action the data
30+
* definition does not allow. Extending Symfony's AccessDeniedException instead of
31+
* DcGeneralRuntimeException lets the security firewall turn this into a proper 403 (or, for an
32+
* anonymous visitor, a redirect to the login form) instead of a 500 error screen - see
33+
* contao-community-alliance/dc-general#543.
34+
*/
35+
abstract class AbstractDefinitionAccessDeniedException extends AccessDeniedException
36+
{
37+
/**
38+
* The definition name of the affected definition.
39+
*
40+
* @var string
41+
*/
42+
protected $name;
43+
44+
/**
45+
* Create instance.
46+
*
47+
* @param string $definitionName The definition name of the affected definition.
48+
* @param \Throwable|null $previous The previous exception.
49+
*/
50+
public function __construct($definitionName, ?\Throwable $previous = null)
51+
{
52+
$this->name = $definitionName;
53+
54+
parent::__construct($this->resolveMessage(), $previous);
55+
}
56+
57+
/**
58+
* The translation key holding the human-readable message for this case.
59+
*
60+
* @return string
61+
*/
62+
abstract protected function translationKey(): string;
63+
64+
/**
65+
* The message used when no translator is available, e.g. in a unit test.
66+
*
67+
* @return string
68+
*/
69+
abstract protected function fallbackMessage(): string;
70+
71+
/**
72+
* Resolve the translated message, falling back gracefully outside a bootstrapped Contao
73+
* framework (e.g. in a unit test constructing the exception directly).
74+
*
75+
* @return string
76+
*/
77+
private function resolveMessage(): string
78+
{
79+
$container = System::getContainer();
80+
// Contao's own docblock claims this is never null, but it genuinely is before the
81+
// framework has booted - e.g. in a plain unit test constructing this exception directly.
82+
/** @psalm-suppress DocblockTypeContradiction */
83+
if (null === $container || !$container->has('translator')) {
84+
return $this->fallbackMessage();
85+
}
86+
87+
$translator = $container->get('translator');
88+
assert($translator instanceof TranslatorInterface);
89+
90+
$message = $translator->trans($this->translationKey(), [], 'dc-general');
91+
92+
return $message !== $this->translationKey() ? $message : $this->fallbackMessage();
93+
}
94+
}

src/Exception/EditOnlyModeException.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
1616
* @author Sven Baumann <baumann.sv@gmail.com>
1717
* @author Richard Henkenjohann <richardhenkenjohann@googlemail.com>
18-
* @copyright 2013-2019 Contao Community Alliance.
18+
* @author Ingolf Steinhardt <info@e-spin.de>
19+
* @copyright 2013-2026 Contao Community Alliance.
1920
* @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later
2021
* @filesource
2122
*/
@@ -25,12 +26,17 @@
2526
/**
2627
* Exception is thrown if an data definition is in edit only mode.
2728
*/
28-
class EditOnlyModeException extends DefinitionException
29+
class EditOnlyModeException extends AbstractDefinitionAccessDeniedException
2930
{
30-
/**
31-
* The message template.
32-
*
33-
* @var string
34-
*/
35-
protected $message = 'Not able to perform action as definition only supports edit actions "%s".';
31+
#[\Override]
32+
protected function translationKey(): string
33+
{
34+
return 'exception.edit_only_mode';
35+
}
36+
37+
#[\Override]
38+
protected function fallbackMessage(): string
39+
{
40+
return 'This view only supports editing existing records.';
41+
}
3642
}

src/Exception/NotCreatableException.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,30 @@
1515
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
1616
* @author Sven Baumann <baumann.sv@gmail.com>
1717
* @author Richard Henkenjohann <richardhenkenjohann@googlemail.com>
18-
* @copyright 2013-2019 Contao Community Alliance.
18+
* @author Ingolf Steinhardt <info@e-spin.de>
19+
* @copyright 2013-2026 Contao Community Alliance.
1920
* @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later
2021
* @filesource
2122
*/
2223

2324
namespace ContaoCommunityAlliance\DcGeneral\Exception;
2425

2526
/**
26-
* Class NotDeletableException.
27+
* Class NotCreatableException.
2728
*
28-
* This exception is thrown if a data definition does not support delete actions.
29+
* This exception is thrown if a data definition does not support create actions.
2930
*/
30-
class NotCreatableException extends DefinitionException
31+
class NotCreatableException extends AbstractDefinitionAccessDeniedException
3132
{
32-
/**
33-
* The message template.
34-
*
35-
* @var string
36-
*/
37-
protected $message = 'Not able to perform action for data definition "%s".';
33+
#[\Override]
34+
protected function translationKey(): string
35+
{
36+
return 'exception.not_creatable';
37+
}
38+
39+
#[\Override]
40+
protected function fallbackMessage(): string
41+
{
42+
return 'New records cannot be created here.';
43+
}
3844
}

src/Exception/NotDeletableException.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
1616
* @author Sven Baumann <baumann.sv@gmail.com>
1717
* @author Richard Henkenjohann <richardhenkenjohann@googlemail.com>
18-
* @copyright 2013-2019 Contao Community Alliance.
18+
* @author Ingolf Steinhardt <info@e-spin.de>
19+
* @copyright 2013-2026 Contao Community Alliance.
1920
* @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later
2021
* @filesource
2122
*/
@@ -27,12 +28,17 @@
2728
*
2829
* This exception is thrown if a data definition does not support delete actions.
2930
*/
30-
class NotDeletableException extends DefinitionException
31+
class NotDeletableException extends AbstractDefinitionAccessDeniedException
3132
{
32-
/**
33-
* The message template.
34-
*
35-
* @var string
36-
*/
37-
protected $message = 'Not able to perform delete action for data definition "%s".';
33+
#[\Override]
34+
protected function translationKey(): string
35+
{
36+
return 'exception.not_deletable';
37+
}
38+
39+
#[\Override]
40+
protected function fallbackMessage(): string
41+
{
42+
return 'This record cannot be deleted.';
43+
}
3844
}

src/Exception/NotEditableException.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
*
1313
* @package contao-community-alliance/dc-general
1414
* @author Richard Henkenjohann <richardhenkenjohann@googlemail.com>
15-
* @copyright 2013-2019 Contao Community Alliance.
15+
* @author Ingolf Steinhardt <info@e-spin.de>
16+
* @copyright 2013-2026 Contao Community Alliance.
1617
* @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later
1718
* @filesource
1819
*/
@@ -26,12 +27,17 @@
2627
*
2728
* @api
2829
*/
29-
class NotEditableException extends DefinitionException
30+
class NotEditableException extends AbstractDefinitionAccessDeniedException
3031
{
31-
/**
32-
* The message template.
33-
*
34-
* @var string
35-
*/
36-
protected $message = 'Not able to perform edit action for data definition "%s".';
32+
#[\Override]
33+
protected function translationKey(): string
34+
{
35+
return 'exception.not_editable';
36+
}
37+
38+
#[\Override]
39+
protected function fallbackMessage(): string
40+
{
41+
return 'This record cannot be edited.';
42+
}
3743
}

src/Resources/translations/dc-general.de.xlf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,5 +509,29 @@
509509
<target>Ende</target>
510510
</segment>
511511
</unit>
512+
<unit id="exception.not_deletable" name="exception.not_deletable">
513+
<segment>
514+
<source>This record cannot be deleted.</source>
515+
<target>Dieser Datensatz kann nicht gelöscht werden.</target>
516+
</segment>
517+
</unit>
518+
<unit id="exception.not_creatable" name="exception.not_creatable">
519+
<segment>
520+
<source>New records cannot be created here.</source>
521+
<target>Hier können keine neuen Datensätze angelegt werden.</target>
522+
</segment>
523+
</unit>
524+
<unit id="exception.not_editable" name="exception.not_editable">
525+
<segment>
526+
<source>This record cannot be edited.</source>
527+
<target>Dieser Datensatz kann nicht bearbeitet werden.</target>
528+
</segment>
529+
</unit>
530+
<unit id="exception.edit_only_mode" name="exception.edit_only_mode">
531+
<segment>
532+
<source>This view only supports editing existing records.</source>
533+
<target>Diese Ansicht unterstützt nur das Bearbeiten vorhandener Datensätze.</target>
534+
</segment>
535+
</unit>
512536
</file>
513537
</xliff>

src/Resources/translations/dc-general.en.xlf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,5 +422,25 @@
422422
<source>End</source>
423423
</segment>
424424
</unit>
425+
<unit id="exception.not_deletable" name="exception.not_deletable">
426+
<segment>
427+
<source>This record cannot be deleted.</source>
428+
</segment>
429+
</unit>
430+
<unit id="exception.not_creatable" name="exception.not_creatable">
431+
<segment>
432+
<source>New records cannot be created here.</source>
433+
</segment>
434+
</unit>
435+
<unit id="exception.not_editable" name="exception.not_editable">
436+
<segment>
437+
<source>This record cannot be edited.</source>
438+
</segment>
439+
</unit>
440+
<unit id="exception.edit_only_mode" name="exception.edit_only_mode">
441+
<segment>
442+
<source>This view only supports editing existing records.</source>
443+
</segment>
444+
</unit>
425445
</file>
426446
</xliff>

0 commit comments

Comments
 (0)