Check the administer operation when checking other registration operations

Created on 17 January 2025, about 2 months ago

Problem/Motivation

Working on ✨ Create additional host permissions Active I realised that:

- On the RegistrationHostAccessControlHandler we check $host->access('administer registrations', ... when we want to consider administer permissions when access checking operations like 'view registrations' etc. So host access hook fires twice, once with 'view registrations' and once with 'administer registrations'.
- But in RegistrationAccessControlHandler, in very similar circumstances, we check the administer permissions directly when evaluating 'view', instead of checking $registration->('administer',...
It's right and important that in the RegistrationHostAccessControlHandler we sometimes check $host->access('manage', which does cause a second firing of the access hook - this is an important point of extensibility. But the consistency with how we're treating administering doesn't seem quite right.

Whether we should check the 'administer registrations' operation inside other host operations (or the 'administer' operation when checking other registration operations), rather than checking these permissions using a helper method instead of an operation, I'm not sure about. The downside of not using the operation (in both host and registration handler) is that if you customise a hook to give a user access to the 'administer' operation, it doesn't give them access to the 'view', 'update' and 'delete' operations automatically which you might expect, based on how the semantic of 'administer' is usually understood in Drupal access. Perhaps in ✨ Make it possible to customise who can administer registrations Active we should have called the operation (on both registration and host) 'override settings' as that is what it actually is about.

Proposed resolution

In RegistrationAccessControlHandler, for view, update etc. operations don't check administer permissions directly. Instead check $registration->access('administer').

This will cause additional access hook invocations for both registration and host, but I don't think we should be concerned about performance: it's cached to only once per request, the classes are already constructed, and there will be very few hook instances.

Remaining tasks

User interface changes

API changes

Data model changes

πŸ“Œ Task
Status

Active

Version

3.3

Component

Registration Core

Created by

πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @jonathanshaw
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    I'm OK with A, but I think B is slightly preferrable as it provides more granular extensibility by the access hooks. With B, an access hook could for example be neutral about deletion while allowing overriding settings, rather than having to allow both when allowing administering.

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    I prefer A, so as to not to introduce even more names of things. When developers see a check for "administer" they know instantly what that means and they can move on mentally. The name "override settings" is amorphous. In the highly unlikely event the granularity is not sufficient, a developer can override the access handler itself or take any number of other approaches.

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    One thing to add to this issue - I notice in some places in the RegistrationAccessControlHandler (I did this in the edit state commit) we orIf a new result. In other places, we replace a result, and thereby lose the cacheability of some earlier result. I wonder if we should use orIf in all places. In practical terms, I am guessing the cacheability will not change for our use cases, but as people add extensions, the possibility some cacheability is lost increases.

    Example code I am thinking of, lines 69-76:

    $result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
    
    if ($result->isNeutral()) {
      $result = $this->checkEntityUserPermissions($entity, $operation, $account);
      // All of these checks depend on the registration type, host or
      // registrant.
      $result->addCacheableDependency($entity);
    }

    Maybe it should become this:

    $result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
    
    if ($result->isNeutral()) {
      $entity_result = $this->checkEntityUserPermissions($entity, $operation, $account);
      // All of these checks depend on the registration type, host or
      // registrant.
      $entity_result->addCacheableDependency($entity);
      $result = $result->orIf($entity_result);
    }
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK
    $result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
    if ($result->isNeutral()) 
      $result = $this->checkEntityUserPermissions($entity, $operation, $account);{

    I get what you mean.

    Ah, yes. A child access control handler extending this one could modify checkEntityUserPermissions and not necessarily use AccessResult::allowedIfHasPermissions().

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    Ready for review. I implemented option A, with some cacheability fixes in there as well.

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    Bump

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    Sorry @jonathan I think the order in which your comments are appearing in the MR is throwing me for a loop. Can you clarify. I believe the gist of what you are looking for is to check "real" operations first, and if they return neutral, then checking administer. But it hard for me to tell.

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    This is ready for review again

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024