Change record status: 
Project: 
Introduced in branch: 
9.3.x
Introduced in version: 
9.3.0
Description: 

Starting with Drupal 9.3.0, all permissions in a user role must be defined in a module.permissions.yml file or a permissions callback. Other permissions are now considered invalid. This includes permissions from uninstalled modules, permissions that depend on configuration that has been removed (such as a content type), and permissions of obscure origin.

In Drupal 9.3.0 or later, saving a role with an invalid permission will trigger a E_USER_DEPRECATED error. In Drupal 10, it will throw a runtime exception.

Since Drupal 8.0, modules are supposed to remove their data from the database when they are uninstalled. See Modules cannot be in a disabled state anymore, only installed and uninstalled. One exception has been that uninstalled modules did not remove the permissions they defined from user roles. Before Drupal 9.3.0, a site administrator could uninstall a module, then install it again, and the permissions previously assigned to user roles would be in effect. Starting with Drupal 9.3.0, the administrator must configure permissions again when re-installing the module.

An update function is provided to remove invalid permissions from existing roles. The update function logs the permissions removed from each role.

Impacts: 
Site builders, administrators, editors
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

maxilein’s picture

How do you solve this if a module did not prove an uninstall hook and you are left with an improper setting?

zenimagine’s picture

I can't update my website to Drupa 10 because PWA creates problems. Forever it is uninstalled. Did you find a solution ?

https://www.drupal.org/files/issues/2023-01-19/Screenshot%202023-01-19%2...

rhovland’s picture

Use a tool like drush to export your configuration drush config:export. Open the offending role files eg user.role.authenticated.yml and delete the line for the orphaned permission.
Then reimport your configuration drush config:import

wxman’s picture

I did as suggested here and keep getting this:

$ drush config:import /var/www/vhosts/site.com/newsite/web/sites/default/files/config_NTZUomU3zGDBwj5U2AD68HPO-24WB2ZHy77qNC6TJCjWKU7CEzn7oasjPwE2vnUMjeQlHzkIkw/sync
+------------+----------------------------------------+-----------+
| Collection | Config                                 | Operation |
+------------+----------------------------------------+-----------+
|            | system.action.profile_delete_action    | Create    |
|            | system.action.profile_publish_action   | Create    |
|            | system.action.profile_unpublish_action | Create    |
|            | views.view.profiles                    | Create    |
|            | core.extension                         | Update    |
|            | user.role.authenticated                | Update    |
|            | user.role.content_author               | Update    |
|            | user.role.administrator                | Update    |
+------------+----------------------------------------+-----------+

 Import the listed configuration changes? (yes/no) [yes]:
 > y

 [error]  Drupal\Core\Config\ConfigImporterException: There were errors validating the config synchronization.
Configuration <em class="placeholder">at_core.settings</em> depends on the <em class="placeholder">at_core</em> extension that will not be installed after import. in Drupal\Core\Config\ConfigImporter->validate() (line 788 of /var/www/vhosts/site.com/newsite/web/core/lib/Drupal/Core/Config/ConfigImporter.php).

In ConfigImportCommands.php line 360:

  The import failed due to the following reasons:
  Configuration <em class="placeholder">at_core.settings</em> depends on the <em class="placeholder">at_core</em> extension that will not be installed after import.
wxman’s picture

I did finally get this fixed. If anyone wants to know, After doing the export, I used good old WinSCP to manually edit the files and save them. I then ran:
drush config:import --source=sites/default/files/config_NTZUomU3zGDBwj5U2AD68HPO-24WB2ZHy77qNC6TJCjWKU7CEzn7oasjPwE2vnUMjeQlHzkIkw/sync
and that fixed it. I don't know why at_core.settings was there because I think that was left over from an old theme.

vensires’s picture

In case you have multiple permissions to fix, you could just use the following PHP script along with drush scr to quickly remove them from your configuration:

<?php
/**
 * @file
 * Script to help cleanup the not existing permissions from your roles.
 *
 * @code
 * drush scr clean_permissions.php
 * drush -y cex
 * @endcode
 *
 * @see https://www.drupal.org/node/3193348
 */
$entity_type_manager = \Drupal::entityTypeManager();
$permissions = array_keys(\Drupal::service('user.permissions')->getPermissions());
/** @var \Drupal\user\RoleInterface[] $roles */
$roles = $entity_type_manager->getStorage('user_role')->loadMultiple();
foreach ($roles as $role) {
  $role_permissions = $role->getPermissions();
  $differences = array_diff($role_permissions, $permissions);
  if ($differences) {
    foreach ($differences as $permission) {
      $role->revokePermission($permission);
    }
    $role->save();
  }
}
Monster971’s picture

Hello @vensires,

Thanks for this answer, where to put the clean_permissions.php script? at the root of the project ?

vensires’s picture

Wherever you like as long as your drush has access to it. I usually have a scripts or scripts_dev folder and store this kind of scripts in there. So, in my case - from the root of the project - I execute ./vendor/bin/drush scr scripts/clean_permissions.php.

Monster971’s picture

Thanks man!

tawellman’s picture

Thank You @vensires!! That made it easy!

lofra’s picture

Hello @vensires,
It work for me, thks you

JeffC518’s picture

Thanks @vensires! I figured this would be possible (to clean these permissions up programmatically) but this saved me a bunch of time. I used the concept and build a drush command out of it. I'd considered creating a module out of this as a dev utility, not sure what the uptake would be though.

E.g. adding a confirmation step in case a permission is tied to a module that someone forgot to install:

public function cleanPermissions() {
    $permissions = array_keys($this->permissionHandler->getPermissions());
    /** @var \Drupal\user\Entity\Role[] $roles */
    $roles = $this->roleStorage->loadMultiple();

    /** @var \Drupal\user\RoleStorageInterface $role */
    foreach ($roles as $role) {
      $role_permissions = $role->getPermissions();
      $diff_permissions_in_role = array_diff($role_permissions, $permissions);
      if ($diff_permissions_in_role) {
        foreach ($diff_permissions_in_role as $permission) {
          $confirm = $this->confirm('Revoke ' . $permission . ' for ' . $role->id() . '?');
          if ($confirm) {
            $this->io()->note('Revoked');
            $role->revokePermission($permission);
          }
        }
        $role->save();
      }
    }
  }

I used DI instead of the static calls if anyone is wondering, e.g.

public function __construct(EntityTypeManagerInterface $entity_type_manager, PermissionHandler $permission_handler) {
    $this->entityTypeManager = $entity_type_manager;
    $this->permissionHandler = $permission_handler;
    $this->roleStorage = $entity_type_manager->getStorage('user_role');
  }

Obviously if you have a lot of permissions, confirming each one gets annoying though. There's probably a way to add a (allow for all) command?

joakland’s picture

@vensires Just wanted to chime in with everyone else and say thanks for this. You saved me a lot of work!

bensti’s picture

@vensires This work really well. Good job. it should be a module :) Thx you

ronttizz’s picture

This saved me a lot! Thank you sir!

generalredneck’s picture

Taking this and throwing it into an update hook on a custom module works great too. just remember to use `drush cex` after, just like any normal workflow.

Umit’s picture

Thanks

awasson’s picture

Thanks. That worked as advertised.

candelas’s picture

@vensires thanks a lot for sharing your knowledge! It works perfect :)

//trying to answer one question for each one that i make.
//this way, drupal will be more friendly and strong

webengr’s picture

Vensires nailed it!!! thanks, was able to use your script and thanks for the @code/@endcode explanation
... I was preparing to upgrade a legacy site that had bad permissions that was orig drupal 6, 7, and 9.5.11 to 10.2 ... this script helped me get past the permissions concerns of upgrade_status. :)

isalmanhaider’s picture

@vensires - Thanks it worked for me on Drupal 10.2.3

pbone3b’s picture

This worked for me too

Dishvola’s picture

It works! Thank you!

ldenna’s picture

Thank you @vensires!
your script is working great!

aitala’s picture

So the PHP script does not seem to work for me... Any ideas?

Thanks,
Eric

__________
Eric Aitala - ema13@psu.edu
Penn State

224b8605113373e086cb27708ff301ba18ce394db1996e7e22928e4555e0d20b1b6cecc7f67c9bd9e536cb915779c485

vensires’s picture

Please provide more information on which is the error you are facing.

aitala’s picture

No errors, it just did not seem to do anything. I ended up exporting config, editing the files by hand, try to re-import those, fix other weird config issues (some involving themes from the D6 era), re-importing again, etc....

E

__________
Eric Aitala - ema13@psu.edu
Penn State

224b8605113373e086cb27708ff301ba18ce394db1996e7e22928e4555e0d20b1b6cecc7f67c9bd9e536cb915779c485

Sseto’s picture

You must've forgotten to add the <?php tag? I added the php tag and it worked.

devsoni’s picture

I have docker image on server, not able to run the script with composer install.

superlolo95’s picture

Script proposed by @vensires worked fine for me.
Thanks.

Drupalite1411’s picture

Hi, script is not working for me. In upgrade status module I am still getting same error.
When I am running script it does show any error.

Anaconda777’s picture

I had only 3 orphaned roles which upgrade status warned. I was able to quickly
remove them with the Drupal UI configuration management.

  1. go to /admin/config/development/configuration/single/export
  2. Export the role and remove the orphaned permissions manually by removing the line (see the permission name from upgrade status warning message).
  3. copy the whole text ctrl+c
  4. go to admin/config/development/configuration/single/import
  5. Slecet configuration type: "role"
  6. paste the edited text and press import

errors gone from upgrade status, making backups of the site can be handy

bburch’s picture

nice way to handle the role junk without scripting!

quchidru’s picture

Finally a solution on so many D10 problems
1 down a lot more to go :(
Thanks a lot

barbarae’s picture

Thank you so much. Fixed my permissions.