Problem/Motivation

When enabling my custom module that implements core access policy API, enabling the module via drush causes all site processes to hang. What is really interesting, is that this only occurs when using $account->hasPermission function inside of my alterPermissions.

Interestingly, the code seemed to work just fine when I added the permission check after the module was already installed.

I have not checked whether the same occurs from calculatePermissions yet.

Visiting the website in browser, after trying this many times, there is a WSOD and memory exhaustion errors, which tend to look like:

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 20480 bytes) in /var/www/html/web/core/lib/Drupal/Core/Session/CalculatedPermissionsItem.php on line 30

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 20480 bytes) in /var/www/html/web/core/lib/Drupal/Core/Database/Connection.php on line 1036

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 32768 bytes) in /var/www/html/web/core/lib/Drupal/Core/Utility/Error.php on line 1

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 20480 bytes) in /var/www/html/web/core/lib/Drupal/Core/Cache/Cache.php on line 29

Steps to reproduce

  1. Create a custom module with a service that extends AccessPolicyBase
  2. Inside of alterPermissions, use the $account->hasPermission function
  3. Try to enable the module

Proposed resolution

Document how to check whether an account has a specific permission from inside of custom access policy implementations.

Remaining tasks

  1. Determine best way to document how to check whether the account has a permission (from inside of an alterPermissions call)
  2. Create that documentation

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3612399

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

megakeegman created an issue. See original summary.

aayushpathak’s picture

Assigned: Unassigned » aayushpathak

working on it !

aayushpathak’s picture

Assigned: aayushpathak » Unassigned
Status: Active » Needs review

Opened a Merge Request to fix the memory exhaustion bug. The issue was caused by an infinite recursion loop when $account->hasPermission() is called from within alterPermissions(). I've added a $processing state tracker to safely short-circuit recursive calls during the calculation phase, along with a dedicated unit test (testRecursiveAccessPolicy) to prevent future regressions. Needs review!

megakeegman’s picture

Status: Needs review » Reviewed & tested by the community

This indeed did fix the issue. I followed the same steps I outlined and was able to install the module without any problem. Thanks!

megakeegman’s picture

Status: Reviewed & tested by the community » Needs work

Oops sorry, false success. I just realized I had the hasPermission call commented out. I tried again, and am still having the same issue.

megakeegman’s picture

Title: Memory issues while enabling module that implements access policy alterPermissions » Calling hasPermission from inside alterPermissions causes memory leak
megakeegman’s picture

So in my case, the reason I am checking whether the account has a permission is because I only want to alter permissions for users that have not been granted a permission to bypass some restrictions. I am wondering about the possibility that this practice was simply not intended. But this does seem like an entirely reasonable check to make.

megakeegman’s picture

From looking around in code, I am starting to think that this should not be done within an AccessPolicy based class, but rather within an AccessResult based class. I don't understand yet how those class types interact, but will be sure to document here if I learn more. Either way, will be changing my approach and will not be calling hasPermission from alterPermissions anymore.

aayushpathak’s picture

Status: Needs work » Needs review

I re-verified this locally using a test module that reproduces the scenario (calling $account->hasPermission() inside alterPermissions(), then drush en). With the MR patch applied to AccessPolicyProcessor.php, the module installs cleanly — no memory exhaustion or infinite recursion.

The fix tracks in-progress calculations in a $processing array, so recursive calls safely return partial permissions instead of looping.

The "Needs Work" status may have been set before the patch was fully applied in the test environment. Happy to share the test module if useful.

megakeegman’s picture

Status: Needs review » Reviewed & tested by the community

Yes, you are right. My patch did not properly apply. I tested again and your patch does address the memory issue. I also reviewed the code with @wolcen and the changes seem minor and effective. I am still curious whether there are other intended practices for how to implement these functions, but moving this back to rbtc. Thanks for the fix.

megakeegman’s picture

I guess one question here. I notice that $this->processing[$processing_key] = $calculated_permissions; gets called prior to merging policy permissions. I am wondering whether the value in the processing array gets updated with those merged permissions.

quietone’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update

This issue does not have the proposed resolution section, which is used by reviewers and committers to understand how the problem is to be fixed. I am tagging for an issue summary. The is related to the Core Documentation Gate. Thanks

kristiaanvandeneynde’s picture

Category: Bug report » Support request
Status: Needs work » Needs review

What is really interesting, is that this only occurs when using $account->hasPermission function inside of my alterPermissions.

Well yes, the system is calculating your permissions and inside that calculation you are asking for the fully calculated permissions :D

From what you wrote on Slack, you're trying to grant a few permissions to whoever has a certain permission. That can indeed be achieved in the alter method and requires no extra cache stuff, provided your list of "extra permissions" never changes.

Otherwise you need a cache invalidation when that does happen e.g.:

  • Extra permissions are configured on a form, so you need to add the config object's cache tag
  • Extra permissions are hard-coded, you need a manual cache rebuild when you change the list
  • Extra permissions depend on other global factors, you need a cache context that represents said global factors

So how do you check what permissions someone has? Well you have all of them in the alter method already!

public function alterPermissions(AccountInterface $account, string $scope, RefinableCalculatedPermissionsInterface $calculated_permissions): void {
  parent::alterPermissions($account, $scope, $calculated_permissions);
  foreach ($calculated_permissions->getItemsByScope($scope) as $item) {
    if ($item->isAdmin()) {
      //No point in adding permissions, they have them all.
      continue;
    }
    if ($item->hasPermission('what you seek')) {
      $new_item = new CalculatedPermissionsItem(
        array_merge($item->getPermissions(), ['new permissions here']),
        FALSE,
        $item->getScope(),
        $item->getIdentifier()
      );
      // This will replace the original with the new one.
      $calculated_permissions->addItem($new_item, TRUE);
    }
  }
}

kristiaanvandeneynde changed the visibility of the branch 3612399-Memory-issues-while-enabling-module to hidden.

megakeegman’s picture

Thanks that is great information, and I will test that right away. As far as a proposed resolution, I wasn't sure when I wrote the issue because I couldn't tell if I was doing something wrong or there was a bug, and yes, I may have set RBTC preemptively. If indeed comment #14 is true, then I would suggest that we turn this into a documentation issue. I would appreciate suggestions for the best way to document, whether that is through recommendation of how to fix the problem when the error occurs, or on a documentation webpage, or otherwise.

megakeegman’s picture

Issue summary: View changes

I can confirm that #14 is correct. I do want to note that this option is not available from inside of calculatePermissions, which makes sense, but was not immediately obvious to me. Is it safe to assume that if you need to do such a check, that you should just be using alterPermissions?

megakeegman’s picture

Status: Needs review » Needs work