- Issue created by @dieterholvoet
- Status changed to Needs review
10 months ago 1:28pm 12 February 2024 - 🇧🇪Belgium Robin.Houtevelts
This might be a good moment to ask this:
Do we really need to define our own AccessHandler?Should we use access hooks instead so we don't have to do this for each module that also wants to define an AccessHandler?
- 🇧🇪Belgium dieterholvoet Brussels
Is this a BC change?
I would say no, that sounds like an edge case we could document in the release notes, but it shouldn't stop us from doing this. We could move it right after the Trash module instead of to the back of the list, that could limit any possible issues, but it isn't a guarantee. I don't really see another way around this, but let me know if you do.
Is that still going to work without modifications?
No.
Should we use access hooks instead?
We can't. The problem is that
Drupal\node\NodeAccessControlHandler
has hardcoded checks for thebypass node access
and theaccess content
permissions: if the first one is present or the second one is missing, the access hooks aren't even invoked. That would mean that the access checks aren't executed for those users, or user 1. - 🇧🇪Belgium Robin.Houtevelts
I see. Thanks for clarifying!
let me know if you do.
No, not without being fonky.
Ideally the AccessHandler would be an actual service we could decorate.
But something like this might make sure we can decorate the previously defined handler too.But it might be a bit too fonky..
function node_singles_entity_type_alter(array &$entityTypes): void { \Drupal::getContainer()->get('state') ->set( SingleNodeAccessControlHandler::STATE_DECORATED_CLASS_KEY, $entityTypes['node']->getHandlerClass('access') ) $entityTypes['node']->setHandlerClass('access', SingleNodeAccessControlHandler::class); $entityTypes['node_type']->setFormClass('delete', SingleNodeTypeDeleteConfirm::class); }
use Drupal\Core\Entity\EntityAccessControlHandlerInterface; use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\State\StateInterface; use Drupal\node\NodeAccessControlHandler; use Drupal\node\NodeAccessControlHandlerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; class SingleNodeAccessControlHandler implements NodeAccessControlHandlerInterface, EntityAccessControlHandlerInterface, EntityHandlerInterface { protected EntityTypeInterface $entityType; protected EntityTypeManagerInterface $entityTypeManager; protected StateInterface $state; protected NodeAccessControlHandlerInterface&EntityAccessControlHandlerInterface $decorated; public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { $self = new self(); $self->entityType = $entity_type; $self->entityTypeManager = $container->get('entity_type.manager'); $self->state = $container->get('state'); return $self; } /** * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) { return $this->decorated()->access($entity, $operation, $account, $return_as_object); } /** * {@inheritdoc} */ public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) { return $this->decorated()->createAccess($entity_bundle, $account, $context, $return_as_object); } /** * {@inheritdoc} */ public function resetCache() { $this->decorated()->resetCache(); } /** * {@inheritdoc} */ public function setModuleHandler(ModuleHandlerInterface $module_handler) { $this->decorated()->setModuleHandler($module_handler); return $this; } /** * {@inheritdoc} */ public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldItemListInterface $items = NULL, $return_as_object = FALSE) { return $this->decorated()->fieldAccess($operation, $field_definition, $account, $items, $return_as_object); } /** * {@inheritdoc} */ public function acquireGrants(NodeInterface $node) { return $this->decorated()->acquireGrants($node); } /** * {@inheritdoc} */ public function writeDefaultGrant() { $this->decorated()->writeDefaultGrant(); } /** * {@inheritdoc} */ public function deleteGrants() { $this->decorated()->deleteGrants(); } /** * {@inheritdoc} */ public function countGrants() { return $this->decorated()->countGrants(); } /** * {@inheritdoc} */ public function checkAllGrants(AccountInterface $account) { return $this->decorated()->checkAllGrants($account); } private function decorated(): NodeAccessControlHandlerInterface&EntityAccessControlHandlerInterface { if (isset($this->decorated)) { return $this->decorated; } $originalAccessHandlerClass = $this->state->get(self::STATE_DECORATED_CLASS_KEY, NodeAccessControlHandler::class); $handler = $this->entityTypeManager->createHandlerInstance($originalAccessHandlerClass, $this->entityType); if (!$handler instanceof NodeAccessControlHandlerInterface) { throw new \InvalidArgumentException('The decorated access control handler must implement NodeAccessControlHandlerInterface.'); } if (!$handler instanceof EntityAccessControlHandlerInterface) { throw new \InvalidArgumentException('The decorated access control handler must implement EntityAccessControlHandlerInterface.'); } return $handler; } }
- 🇧🇪Belgium dieterholvoet Brussels
I just tried it out and this would work, but only if all contrib modules overriding the node access handler implement it this way.
trash_entity_type_alter()
is executed afternode_singles_entity_type_alter()
, so without a change in the Trash module this won't work. I will start a new branch on the issue fork with this implementation anyway. - 🇧🇪Belgium Robin.Houtevelts
Isn't that fixed by the
node_singles_module_implements_alter
added in this MR?
Then we decorate whatever was final. - 🇧🇪Belgium Robin.Houtevelts
Cool!
I'm actually planned to update one of our projects to 10.2 this week.
That project currently has a module whoseHOOK_entity_type_alter
appears to run afternode_singles
too -just like Trash- but *doesn't* extend/wrap this AccessHandler.So I think I can test this exact use case.
I'll test this once I get to 10.2 somewhere this or next week, and let you know here. - Status changed to RTBC
9 months ago 2:27pm 19 March 2024 -
DieterHolvoet →
committed e956cc6a on 3.x
Issue #3420828 by DieterHolvoet, Robin.Houtevelts: Add support for the...
-
DieterHolvoet →
committed e956cc6a on 3.x
- Status changed to Fixed
9 months ago 3:04pm 19 March 2024 Automatically closed - issue fixed for 2 weeks with no activity.