-
Notifications
You must be signed in to change notification settings - Fork 653
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[!!!][TASK] Extract record access checks from TSFE
Record access checks are moved from TSFE to the new RecordAccessVoter class. This encapsulates corresponding logic at a central place. In addition, the existing hook $GLOBALS[TYPO3_CONF_VARS][SC_OPTIONS][tslib/class.tslib_fe.php][hook_checkEnableFields] is removed in favor of the new PSR-14 RecordAccessGrantedEvent. Resolves: #96996 Releases: main Change-Id: Ic056eb4c62d9792ee62198ae346db5231576d1bb Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73638 Tested-by: core-ci <typo3@b13.com> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Benni Mack <benni@typo3.org>
- Loading branch information
Showing
12 changed files
with
561 additions
and
60 deletions.
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
typo3/sysext/core/Classes/Domain/Access/RecordAccessGrantedEvent.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the TYPO3 CMS project. | ||
* | ||
* It is free software; you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License, either version 2 | ||
* of the License, or any later version. | ||
* | ||
* For the full copyright and license information, please read the | ||
* LICENSE.txt file that was distributed with this source code. | ||
* | ||
* The TYPO3 project - inspiring people to share! | ||
*/ | ||
|
||
namespace TYPO3\CMS\Core\Domain\Access; | ||
|
||
use Psr\EventDispatcher\StoppableEventInterface; | ||
use TYPO3\CMS\Core\Context\Context; | ||
|
||
/** | ||
* Event to modify records to be checked against "enableFields". | ||
* Listeners are able to grant access or to modify the record itself to | ||
* continue to use the native access check functionality with a modified dataset. | ||
*/ | ||
final class RecordAccessGrantedEvent implements StoppableEventInterface | ||
{ | ||
private ?bool $accessGranted = null; | ||
|
||
public function __construct( | ||
private readonly string $tableName, | ||
private array $record, | ||
private readonly Context $context | ||
) { | ||
} | ||
|
||
public function isPropagationStopped(): bool | ||
{ | ||
return $this->accessGranted !== null; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public function accessGranted(): bool | ||
{ | ||
if ($this->accessGranted === null) { | ||
throw new \RuntimeException('Access was not yet defined.', 1645506529); | ||
} | ||
|
||
return $this->accessGranted; | ||
} | ||
|
||
public function setAccessGranted(bool $accessGranted): void | ||
{ | ||
$this->accessGranted = $accessGranted; | ||
} | ||
|
||
public function getTable(): string | ||
{ | ||
return $this->tableName; | ||
} | ||
|
||
public function getRecord(): array | ||
{ | ||
return $this->record; | ||
} | ||
|
||
public function updateRecord(array $record): void | ||
{ | ||
$this->record = $record; | ||
} | ||
|
||
public function getContext(): Context | ||
{ | ||
return $this->context; | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
typo3/sysext/core/Classes/Domain/Access/RecordAccessVoter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the TYPO3 CMS project. | ||
* | ||
* It is free software; you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License, either version 2 | ||
* of the License, or any later version. | ||
* | ||
* For the full copyright and license information, please read the | ||
* LICENSE.txt file that was distributed with this source code. | ||
* | ||
* The TYPO3 project - inspiring people to share! | ||
*/ | ||
|
||
namespace TYPO3\CMS\Core\Domain\Access; | ||
|
||
use Psr\EventDispatcher\EventDispatcherInterface; | ||
use TYPO3\CMS\Core\Context\Context; | ||
|
||
/** | ||
* Checks if a record can be accessed (usually in TYPO3 Frontend) due to various "enableFields" or group access checks. | ||
* | ||
* Not related to "write permissions" etc. | ||
*/ | ||
class RecordAccessVoter | ||
{ | ||
public function __construct( | ||
protected readonly EventDispatcherInterface $eventDispatcher | ||
) { | ||
} | ||
|
||
/** | ||
* Checks page record for enableFields | ||
* Returns TRUE if enableFields does not disable the page record. | ||
* Takes notice of the includeHiddenPages visibility aspect flag and uses SIM_ACCESS_TIME for start/endtime evaluation | ||
* | ||
* @param string $table the TCA table to check for | ||
* @param array $record The record to evaluate (needs fields: hidden, starttime, endtime, fe_group) | ||
* @param Context $context Context API to check against | ||
* @return bool TRUE, if record is viewable. | ||
*/ | ||
public function accessGranted(string $table, array $record, Context $context): bool | ||
{ | ||
$event = new RecordAccessGrantedEvent($table, $record, $context); | ||
$this->eventDispatcher->dispatch($event); | ||
if ($event->isPropagationStopped()) { | ||
return $event->accessGranted(); | ||
} | ||
$record = $event->getRecord(); | ||
|
||
$configuration = $this->getEnableFieldsConfigurationForTable($table); | ||
$visibilityAspect = $context->getAspect('visibility'); | ||
$includeHidden = $table === 'pages' | ||
? $visibilityAspect->includeHiddenPages() | ||
: $visibilityAspect->includeHiddenContent(); | ||
|
||
// Hidden field is active and hidden records should not be included | ||
if (($record[$configuration['disabled'] ?? null] ?? false) && !$includeHidden) { | ||
return false; | ||
} | ||
// Records' starttime set AND is HIGHER than the current access time | ||
if (isset($configuration['starttime'], $record[$configuration['starttime']]) | ||
&& (int)$record[$configuration['starttime']] > $GLOBALS['SIM_ACCESS_TIME'] | ||
) { | ||
return false; | ||
} | ||
// Records' endtime is set AND NOT "0" AND LOWER than the current access time | ||
if (isset($configuration['endtime'], $record[$configuration['endtime']]) | ||
&& ((int)$record[$configuration['endtime']] !== 0) | ||
&& ((int)$record[$configuration['endtime']] < $GLOBALS['SIM_ACCESS_TIME']) | ||
) { | ||
return false; | ||
} | ||
// Insufficient group access | ||
if ($this->groupAccessGranted($table, $record, $context) === false) { | ||
return false; | ||
} | ||
// Record is available | ||
return true; | ||
} | ||
|
||
/** | ||
* Check group access against a record, if the current users' groups match the fe_group values of the record. | ||
* | ||
* @param string $table the TCA table to check for | ||
* @param array $record The record to evaluate (needs enableField: fe_group) | ||
* @param Context $context Context API to check against | ||
* @return bool TRUE, if group access is granted. | ||
*/ | ||
public function groupAccessGranted(string $table, array $record, Context $context): bool | ||
{ | ||
if (!$context->hasAspect('frontend.user')) { | ||
return true; | ||
} | ||
$configuration = $this->getEnableFieldsConfigurationForTable($table); | ||
if (!isset($configuration['fe_group']) || !($record[$configuration['fe_group']] ?? false)) { | ||
return true; | ||
} | ||
$pageGroupList = explode(',', (string)$record[$configuration['fe_group']]); | ||
return count(array_intersect($context->getAspect('frontend.user')->getGroupIds(), $pageGroupList)) > 0; | ||
} | ||
|
||
/** | ||
* Checks if the current page of the root line is visible. | ||
* | ||
* If the field extendToSubpages is 0, access is granted, | ||
* else the fields hidden, starttime, endtime, fe_group are evaluated. | ||
* | ||
* @internal this is a special use case and should only be used with care, not part of TYPO3's Public API. | ||
*/ | ||
public function accessGrantedForPageInRootLine(array $pageRecord, Context $context): bool | ||
{ | ||
return !($pageRecord['extendToSubpages'] ?? false) || $this->accessGranted('pages', $pageRecord, $context); | ||
} | ||
|
||
protected function getEnableFieldsConfigurationForTable(string $table): array | ||
{ | ||
return $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'] ?? []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
...re/Documentation/Changelog/12.0/Breaking-96996-HookCheckEnableFieldsRemoved.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
.. include:: ../../Includes.txt | ||
|
||
=================================================== | ||
Breaking: #96996 - Hook "checkEnableFields" removed | ||
=================================================== | ||
|
||
See :issue:`96996` | ||
|
||
Description | ||
=========== | ||
|
||
The previous TYPO3 Hook "hook_checkEnableFields" registered via | ||
:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields']` | ||
has been removed in favor of a new PSR-14 Event | ||
:php:`TYPO3\CMS\Core\Domain\Access\RecordAccessGrantedEvent`. | ||
|
||
Impact | ||
====== | ||
|
||
Hooks in third-party extensions will not be executed anymore. | ||
|
||
Affected Installations | ||
====================== | ||
|
||
TYPO3 installations with custom extensions using this hook. The | ||
extension scanner will notify about usages. | ||
|
||
Migration | ||
========= | ||
|
||
Register a new PSR-14 event listener for :php:`RecordAccessGrantedEvent` | ||
in the extensions' :file:`Services.yaml` to keep TYPO3 v12+ compatibility. | ||
|
||
Extensions can then provide compatibility with TYPO3 v11 and TYPO3 v12 at | ||
the same time. | ||
|
||
.. index:: Frontend, PHP-API, FullyScanned, ext:frontend |
35 changes: 35 additions & 0 deletions
35
...0/Deprecation-96996-DeprecateTypoScriptFrontendController-checkEnableFields.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
.. include:: ../../Includes.txt | ||
|
||
=============================================================================== | ||
Deprecation: #96996 - Deprecate TypoScriptFrontendController->checkEnableFields | ||
=============================================================================== | ||
|
||
See :issue:`96996` | ||
|
||
Description | ||
=========== | ||
|
||
The :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->checkEnableFields()` | ||
method has been deprecated in favour of the new :php:`TYPO3\CMS\Core\Domain\Access\RecordAccessVoter` | ||
component. | ||
|
||
Impact | ||
====== | ||
|
||
:php:`TypoScriptFrontendController->checkEnableFields()` will raise a | ||
deprecation level log entry when called. The extension scanner will | ||
report usages as weak match. | ||
|
||
Affected Installations | ||
====================== | ||
|
||
All installations calling :php:`TypoScriptFrontendController->checkEnableFields()` | ||
in custom extension code. | ||
|
||
Migration | ||
========= | ||
|
||
Replace all usages of the deprecated method. Use the :php:`RecordAccessVoter` | ||
component instead, e.g. :php:`RecordAccessVoter->accessGranted()`. | ||
|
||
.. index:: Frontend, PHP-API, FullyScanned, ext:frontend |
64 changes: 64 additions & 0 deletions
64
.../Changelog/12.0/Feature-96996-PSR-14EventForModifyingRecordAccessEvaluation.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
.. include:: ../../Includes.txt | ||
|
||
===================================================================== | ||
Feature: #96996 - PSR-14 Event for modifying record access evaluation | ||
===================================================================== | ||
|
||
See :issue:`96996` | ||
|
||
Description | ||
=========== | ||
|
||
A new PSR-14 event :php:`RecordAccessGrantedEvent` has been added. It serves | ||
as replacement for the now removed hook | ||
:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields']`. | ||
|
||
The new PSR-14 event can be used to either define whether record access is granted | ||
for a user, or to even modify the record in question. In case the `$accessGranted` | ||
property is set (either :php:`true` or :php:`false`), the defined settings is | ||
directly used, skipping any further event listener as well as any further | ||
evaluation. | ||
|
||
Example | ||
======= | ||
|
||
Registration of the Event in your extensions' :file:`Services.yaml`: | ||
|
||
.. code-block:: yaml | ||
MyVendor\MyPackage\MyEventListener: | ||
tags: | ||
- name: event.listener | ||
identifier: 'my-package/set-access-granted' | ||
The corresponding event listener class: | ||
|
||
.. code-block:: php | ||
use TYPO3\CMS\Core\Domain\Access\RecordAccessGrantedEvent; | ||
class MyEventListener { | ||
public function __invoke(RecordAccessGrantedEvent $event): void | ||
{ | ||
// Manually set access granted | ||
if ($event->getTable() === 'my_table' && ($event->getRecord()['custom_access_field'] ?? false)) { | ||
$event->setAccessGranted(true); | ||
} | ||
// Update the record to be checked | ||
$record = $event->getRecord(); | ||
$record['some_field'] = true; | ||
$event->updateRecord($record); | ||
} | ||
} | ||
Impact | ||
====== | ||
|
||
With the new PSR-14 :php:`RecordAccessGrantedEvent`, it's | ||
now possible to manipulate the record access evaluation by | ||
either directly granting access or by modifying the record | ||
to be evaluated. | ||
|
||
.. index:: Frontend, PHP-API, ext:frontend |
Oops, something went wrong.