Problem/Motivation

In our Drupal site, we are extending ConfigFactoryOverrideInterface and overriding the loadOverrides function to assign permissions to a user role dynamically. This functionality works as expected in Drupal version 10.2.7. However, starting from Drupal version 10.3.0, the loadOverrides method is no longer invoked after creating a new content type, which causes our dynamic permission assignment to fail.

Steps to reproduce

Implement ConfigFactoryOverrideInterface and override the loadOverrides function to assign permissions to a user role dynamically.
Create a new content type, e.g., 'test_content'.

Observe that in Drupal 10.2.7, the loadOverrides method is invoked, and permissions are assigned correctly.
Upgrade to Drupal 10.3.0 or later.
Create the same content type 'test_content'.
Observe that the loadOverrides method is not invoked, and permissions are not assigned.

Starting from Drupal 10.3.0, the loadOverrides method is not invoked after creating a new content type, causing dynamic permission assignments to fail.

Code snippets demonstrating the ConfigFactoryOverrideInterface implementation.

 class PermissionOverrides implements ConfigFactoryOverrideInterface {

/**
  * {@inheritdoc}
  */
public function loadOverrides($names) {
  $overrides = [];
  $role = 'user.role.test_role';
  if (in_array($role, $names)) {
    $types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple();
    foreach ($types as $type) {
      $permission = 'Test permission ' . $type->id();
      $overrides[$role]['permissions'][$permission] = $permission;
    }
  }

  return $overrides;
}

/**
 * @return string
 */
public function getCacheSuffix() {
  return static::class;
}

/**
 * @param string $name
 *
 * @return \Drupal\Core\Cache\CacheableMetadata
 */
public function getCacheableMetadata($name) {
  return new CacheableMetadata();
}

/**
 * @param mixed $name
 * @param string $collection
 *
 * @return \Drupal\Core\Config\StorableConfigBase|null
 */
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
  return NULL;
}

}

Code snippets for Content type creation.

$this->drupalCreateContentType(['type' => 'test_content', 'name' => 'test_content']);

Proposed resolution

The loadOverrides method should be invoked after creating a new content type, as it does in Drupal 10.2.7, to ensure dynamic permission assignments work correctly.

Comments

ananthakrishnan.kr created an issue. See original summary.

balramchauhan684’s picture

public function getCacheSuffix() {
return 'key_config_override';
}

Try changing return value of getCacheSuffix function and test it again.

ananthakrishnan.kr’s picture

Issue summary: View changes
ananthakrishnan.kr’s picture

I tried changing return value of getCacheSuffix function. But still we have the issue.

quietone’s picture

Version: 10.3.x-dev » 11.x-dev

Changes are made on on 11.x (our main development branch) first, and are then back ported as needed according to our policies.

cilefen’s picture

Can you execute a git bisect on a Git working copy of Drupal Core between 10.2.7 and 10.3.0 to quickly find the commit that changed this behavior?

nitesh624’s picture

will git bisect show the code difference between these two versions?

nitesh624’s picture

I ran the bisect between tags 10.2.7 and 10.3.1 by running below command

git bisect start
git bisect good tags/10.2.7
git bisect bad tags/10.3.1 

then the commit which I get is setting the drupal core to 11.0-dev which leads to break testing.

nitesh624’s picture

This https://git.drupalcode.org/project/drupal/-/commit/9bb46296cee3aa33a9750... is the commit on Mar 27, 2024 which causing our test to failure.

kristiaanvandeneynde’s picture

We never touched config overrides in that issue. The only thing that has changed is that we cache the outcome of your permission calculations, including the ones coming from roles, because that's the only way to safely do so.

Altering permissions live is a security risk, the code in the IS is unsafe. I specifically designed the Access Policy API because I too needed alterable permissions but found out that dynamically changing what permissions a role has is an attack vector. I shall not go into further detail on that matter because you apparently have such code in your system.

My advice would be to introduce an access policy to take care of whatever logic you had in your config overrides. Then it will work and be safe.

tlo405’s picture

I'm actually having a similar problem in my project...

We have some functionality that allows an anonymous user to view an unpublished page via a URL hash. We have an existing functional test that basically visits an unpublished page (say node/1) and we check for a 403. Then we visit the page again with the hash as part of the URL node/1?hash=validHashHere and check for a 200. We are now receiving a 403 after the 10.3.1 update.

It seems that the above commit in question definitely has something to do with it (especially with the addition of permissionsHashGenerator in AccountPermissionsCacheContext.php). Still debugging though...

nitesh624’s picture

I have update my custom code to assign permission through access policy but facing same issue

nitesh624’s picture

Here is my code snippet
-> custom_access/src/Access/AccessUnpublishedPermission.php

<?php

namespace Drupal\custom_access\Access;

use Drupal\Core\Session\AccessPolicyBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\CalculatedPermissionsItem;
use Drupal\Core\Session\RefinableCalculatedPermissionsInterface;

/**
 * Using the access policy API to allow  users to view unpublished content.
 */
class AccessUnpublishedPermission extends AccessPolicyBase {

  /**
   * {@inheritdoc}
   */
  public function calculatePermissions(AccountInterface $account, string $scope): RefinableCalculatedPermissionsInterface {
    $calculated_permissions = parent::calculatePermissions($account, $scope);
    $types = \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple();
    $permission = [];
    foreach ($types as $type) {
      $permission[] = 'access_unpublished node ' . $type->id();
    }

    // If we aren't an anonymous user, return early.
    if (!in_array('anonymous', $account->getRoles())) {
      return $calculated_permissions;
    }

    return $calculated_permissions->addItem(new CalculatedPermissionsItem($permission));
  }

  public function getPersistentCacheContexts(): array {
    return ['user.roles:anonymous'];
  }

}

-> custom_access.services.yml

services:
  custom_access.access_policy.access_unpublished_permission:
    class: Drupal\custom_access\Access\AccessUnpublishedPermission
    tags:
      - { name: access_policy }

Then in Functional Test I am creating a test content type like below
$this->drupalCreateContentType(['type' => 'test_content', 'name' => 'test_content']);

The problem is after creating the content type like this in test, \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple();
service in AccessUnpublishedPermission.php file does not include the test_content content type

nitesh624’s picture

I agreed with https://www.drupal.org/project/drupal/issues/3463722#comment-15717005. But will the caching changes affect the outcome of
$types = \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple(); in above code snippet, when we are creating node in a Functional test like $this->drupalCreateContentType(['type' => 'test_content', 'name' => 'test_content']); ?

kristiaanvandeneynde’s picture

No, loading node types should still work

nitesh624’s picture

But in my case newly created node
Type in functional test is not coming in config override service, Unless we forcefully clear the cache in functional test.

So somthing cache related in permission policy api causing this behaviour in our drupal site

kristiaanvandeneynde’s picture

I cannot judge this properly because I have no overview of your custom code. That said, the tests in core clearly show that node retrieval does work after the implementation of the access policy API. The cache in the access policy API only caches your calculated permissions, nothing else.

nitesh624’s picture

Code snippet is there in the issue summary

kristiaanvandeneynde’s picture

But I already explained why that piece of code won't work and is dangerous. Try your access policy, and only that access policy, in a clean Drupal install and see if things are still broken. The access policy API does not affect node loading, so clearly something is going on here.

kristiaanvandeneynde’s picture

Oh hold up, I get it now. You forgot to set the cacheability. You rely on the list of node types, but never mention that anywhere. So of course you are getting the cached result of the previously configured node types.

nitesh624’s picture

Yes with access policy approach we were able to get that working after adding cacheability .

But I am asking about the config override approach (below code)

class PermissionOverrides implements ConfigFactoryOverrideInterface {

/**
  * {@inheritdoc}
  */
public function loadOverrides($names) {
  $overrides = [];
  $role = 'user.role.test_role';
  if (in_array($role, $names)) {
    $types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple();
    foreach ($types as $type) {
      $permission = 'Test permission ' . $type->id();
      $overrides[$role]['permissions'][$permission] = $permission;
    }
  }

  return $overrides;
}

/**
 * @return string
 */
public function getCacheSuffix() {
  return static::class;
}

/**
 * @param string $name
 *
 * @return \Drupal\Core\Cache\CacheableMetadata
 */
public function getCacheableMetadata($name) {
  return new CacheableMetadata();
}

/**
 * @param mixed $name
 * @param string $collection
 *
 * @return \Drupal\Core\Config\StorableConfigBase|null
 */
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
  return NULL;
}

}

Code snippets for Content type creation in Functional Test.

$this->drupalCreateContentType(['type' => 'test_content', 'name' => 'test_content']);

The above code is not working after inclusion of permission policy api. Means the test_content content type which we are creating in Functional test is not coming in the $types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple();

When I debug more on this I found that on web/core/lib/Drupal/Core/Session/PermissionChecker.php file under hasPermission(), When I reverted back the changes committed in https://git.drupalcode.org/project/drupal/-/commit/9bb46296cee3aa33a9750... commit, Then my config override method is working fine

kristiaanvandeneynde’s picture

Category: Bug report » Support request

Yes with access policy approach we were able to get that working after adding cacheability . But I am asking about the config override approach.

I already told you in #10 that that is unsafe.

I gave you a solution, you told me it works, so why not use that? Also at this point this is probably better categorized as a support request.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

quietone’s picture

Status: Active » Fixed

Skimming through this I see that @kristiaanvandeneynde has answered the queries.

I am closing this per the guidance in Handle or refer a support request in an issue.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.