Problem/Motivation
Permissions from contrib modules are migrated "as is" from Drupal 7 even if there is no Drupal 9 module providing the given permission. These permissions appear in the yaml file exports for each role, but do not appear in the UI, and there's no way to clean these up except by scanning through the file to manually remove them from each role, then run configuration import to delete from the database.
Steps to reproduce (Drupal 9):
1) Drupal 7 site contains backup_migrate
module.
2) Drupal 7 contains a custom "developer" role (aside from standard Admin) role.
2) Drupal 7 developer role has permissions to use backup and migrate.
3) Do not install Backup Migrate on Drupal 9.
4) Run migrations, export config with drush.
Expected behavior:
See no mention of backup-related permissions in the exported user.role.developer.yml
file.
What happened instead?
Yaml file contains lines related to Drupal 7 backup & migrate permissions.
- 'access backup and migrate'
- 'access backup files'
- 'perform backup'
Steps to reproduce (Drupal 10):
See Comments #133, #135. Use the custom migration test_user_role.
- Install Drupal and enable the Migrate module:
drush si standard
anddrush en migrate
. - Run the migration:
drush mim test_user_role
.
These steps lead to the following error message:
[error] Adding non-existent permissions to a role is not allowed. The incorrect permissions are "delete any forum content", "foo test permission". (/var/www/html/core/modules/user/src/Entity/Role.php:206)
Proposed resolution
Remove the skip_missing_permission_deprecation
flag that was added to the Role entity in #2571235: [regression] Roles should depend on objects that are building the granted permissions and used in the core role migrations.
Add a new UserRole destination plugin that identifies and drops any non-existing permissions. Any role that has non-existing permissions will have a message in the migrate_message table and in the Drupal log that lists all the dropped permissions.
Add optional dependencies to the core role migrations so that permissions that depend on migrated configuration will be valid when the role migration is run.
Remaining tasks
Completed tasks
- Update the change record. In particular, show how a contrib module can use
hook_migration_plugins_alter()
to add dependencies to the core role migrations. - Review the release-notes snippet.
- Review the change record.
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
The entity:user_role
migrate destination plugin checks for invalid permissions in a migrated role. If there are any, they are removed and they are listed in a log message and a migrate message so that a site administrator can review them later.
Comment | File | Size | Author |
---|---|---|---|
#235 | interdiff-2953111-207-231.txt | 7.01 KB | benjifisher |
#231 | 2953111-231.patch | 30.4 KB | quietone |
| |||
#231 | interdiff-227-231.txt | 647 bytes | quietone |
#227 | 2953111-227.patch | 30.4 KB | quietone |
#227 | interdiff-223-227.txt | 1.94 KB | quietone |
Comments
Comment #2
jwilson3Comment #3
jwilson3Comment #4
jwilson3Comment #7
quietone CreditAttribution: quietone as a volunteer commentedComment #11
quietone CreditAttribution: quietone as a volunteer commentedthis adds a destination plugin for entity:user_role that will filter out permissions that do not exist on the destination.
Comment #12
quietone CreditAttribution: quietone as a volunteer commentedComment #13
quietone CreditAttribution: quietone as a volunteer commentedChanging to Critical because #3055557: Role migration results in migration permissions that do not exist on the target site is critical.
Comment #15
quietone CreditAttribution: quietone as a volunteer commentedDue to #3192893: [META] Serialization issues in Migration tests I had to use the debugger to get the exception messages. They all fail when getting all the permissions in the new EntityUserRole destination,
The exception messages are:
d6/MigrateBlockContentTranslationTest.php
'Route "entity.filter_format.edit_form" does not exist.'
d7/MigrateBlockContentTranslationTest.php and d6/MigrateTermNodeTranslationTest.php
'You have requested a non-existent service "locale.config_manager".'
The errors are fixed by adding 'locale' to the list of modules installed in the test. I do not understand why a missing filter_format route is fixed by installing locale nor why the permission handler is requesting 'locale.config_manager' when it does not exist. This seems like a bug somewhere.
Comment #16
quietone CreditAttribution: quietone as a volunteer commentedThis affects d6 source as well.
Comment #17
quietone CreditAttribution: quietone as a volunteer commentedComment #18
quietone CreditAttribution: quietone as a volunteer commentedComment #19
andypostit could use BC compatible patterns like https://git.drupalcode.org/project/drupal/-/blob/9.2.x/core/modules/work...
Comment #20
quietone CreditAttribution: quietone as a volunteer commentedOK, changes made for #19.
Comment #21
benjifisherI am starting to review this. If I forget to un-assign myself later today, feel free to reassign this issue.
Comment #22
benjifisherThe new destination class is very simple: kudos to @quietone and to everyone who helped design the migration framework! This class defines the
entity:user_role
destination plugin. Without this patch, that plugin is provided by the deriver from theEntityConfigBase
class.I looked into the
user.permissions
service,Drupal\user\PermissionHandler
, to see howgetPermissions()
is implemented. At first, I was worried by method names likebuildPermissionsYaml()
, since I thought that would just find static permissions defined on YAML files. Looking a little closer, I see that permissions callbacks are declared in the YAML files, and those callbacks are invoked. Just to be sure, I tested\Drupal::service('user.permissions')->getPermissions()
to make sure that dynamic permissions likeview article revisions
are included.So far, so good!
My main concern is dependencies.
For the core migrations, we have to declare a dependency on any migration that (indirectly) creates new permissions. The obvious examples are the
d6_node_type
andd7_node_type
migrations, which create things like thearticle
content type and then theview article revisions
permission, for example.Is an optional dependency good enough? I think so.
We should review all the
.permission.yml
files in core modules to find other callbacks, in case there are less obvious examples. I hope we are not creating circular dependencies. Are there any config migrations that depend ond6_user_role
ord7_user_role
?The other thing we have to do is warn anyone developing a custom migration to add similar dependencies. In practice, this should not be a big problem: after considering dependencies, migrations are normally run in alphabetical order (I think), so the
user_role
migrations should run near the end of the config migrations. (Do content migrations have some sort of implicit dependence on config migrations, or does that have to be declared explicitly?) But a custom migration might create a content type in a migration without thed6_
ord7_
prefix, and that could cause a problem.We need at least a change record, and perhaps also a release note.
The change record should also mention that relevant modules should be enabled before running the migrations. For example, if the source site has the
backup_migrate
module enabled, as described in the issue summary, then until now there is no need to enable it on the destination site before migration. Now there is.Could we add an option to the destination plugin and, if set, save a message when roles are filtered out?
There are 9 modules that define permission callbacks:
I will have a look at the tests, but I am adding the tag for a change record and setting this issue to NW for the dependencies.
Comment #23
quietone CreditAttribution: quietone as a volunteer commented@benjifisher, thanks for reviewing this critical.
Ran out of steam just as I was about to leave a comment so this is very brief.
Yes, d6_block and d7_block.
Added the logging of a message to the destination plugin and updated the tests. Also started the change record.
Comment #25
quietone CreditAttribution: quietone as a volunteer commentedLooks like it is failing on ordering. This adds the source rid as the index to the array of expected messages. Maybe that will help.
Comment #26
quietone CreditAttribution: quietone as a volunteer commentedSilly mistake.
Comment #27
quietone CreditAttribution: quietone as a volunteer commentedAgain? I don't understand how I keep making this mistake. I guess I should not be making a patch and just before or after an America's Cup race.
Comment #28
benjifisher+1 for always logging the permissions that are dropped. My suggestion to add an option for that was over complicated.
It is moderately expensive to calculate the permissions: read a bunch of YAML files from disk, parse them, run 9 callback functions (more or less). Will it save time if we do this once in the constructor/
create()
method and save the result in a class variable? Plugins are only instantiated once, right?I did not number the tasks in my previous comment. Instead, I will update the "Remaining tasks" section in the issue description. One item is additional test coverage. I see the test that "blog" permissions are logged, but we also need a test that these permissions will be migrated if the
d?_node_type
migration is run first.Comment #29
quietone CreditAttribution: quietone as a volunteer commented28.
paragraph 2. I thought the same thing but didn't implement it, don't know why anymore. It has been added.
paragraph 3. Added a test that does the node_type migration first.
I have not added the dependency on node_type to the user role migration.
Comment #30
quietone CreditAttribution: quietone as a volunteer commentedYes, I did it again.
Comment #31
benjifisher@quietone, thanks for the improvements!
The change to
EntityUserRole
looks good, saving the list of all permissions in thecreate()
method.There are a lot of changes to the tests. In general, the test coverage is great, but I have some suggestions below.
Back to NW for these changes and for the remaining items listed in the issue summary.
Can you put all the
assert*()
helper methods together? I do not feel strongly about whether they should go at the top of the file or the bottom. The existing helpers are at the top, so I would put the new ones there, unless you care more than I do.Some of the new methods need type declarations and/or
@param
comments and/or the@param
comments need type declarations. (But see the next point before fixing all of these.)In the D6 test, the
assertRoles()
method is always called with the first parameter set to$this->expectedRoleData
. It would be simpler to letassertRoles()
access the class variable directly, so it does not need the new$data
parameter.The D6 test sorts the permissions in
assertRole()
, so we could usearray_merge()
orarray_push()
here to add just the permissions that are added (indirectly) by thed6_node_type
migration. That would emphasize that this is testing what I asked for in #28. On the other hand, this would make the tests less explicit, so maybe it is best to leave it as is.Same snippet: either way, adjust the comments. Make the first something like "After the d6_node_type migration, there are additional roles." I think we can remove the second.
The
$roles
variable does not change between the two definitions, so we could remove the second. If we do that, then we should probably give it a more descriptive name, maybe$duplicate_role_names
.Same snippet: consider changing the second comment to "Test there are still no …".
P.S. (8):
It is a warning message, not an error message.
Comment #32
quietone CreditAttribution: quietone as a volunteer commented@benjifished, thanks for the review. I was initially cautious about further changes to the tests but on reading through they are all sensible and stay within scope.
#31.
1. Moved the helpers to the top to minimize the changes.
2. Fixed
3. Fixed
4. OK, leaving as is
5. Fixed
6. Fixed
7, Fixed
8. Fixed
Comment #33
quietone CreditAttribution: quietone as a volunteer commentedNow add an optional dependency on the node type migration.
This requires some changes to the D6 test. So far, this issue has added a test to d6/MigrateUserRole that added the running of d6_node_type, so there are two tests one without running d6_node_type (testUserRole) and one with running d6_node_type (testUserRoleWithNodeType). However, now that the optional dependency has been added it is no longer a simple task to run the test that does not run d6_node_type. That is because \Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase installs 7 modules, one of which is 'node'. That means that the node migrations are discovered and d6_user_role now requires d6_node_type. Because of the recently added test (testUserRoleWithNodeType) has been removed and the existing test (testUserRole) modified to handle the running of d6_node_type. This is all fine, when a dependency is added the migration test is altered.
For the companion d7/MigrateUserRole this is not a problem, the base class does not install modules so it can have both a testUserRole and a testUserRolwWithNodeType.
I should add that I tried a quick solution of uninstalling node in the D6 test and that fails with
The test could be done by adding a test module that alters d6_user_role but that isn't needed - we know the dependency system works as we can see with the d7 test.
Comment #35
quietone CreditAttribution: quietone as a volunteer commentedAdding the dependency to d6_user_role broke a lot of tests because of the problem mentioned about the base class enabling modules.
Comment #37
quietone CreditAttribution: quietone as a volunteer commentedMissed two tests.
Comment #38
benjifisher@quietone:
While I was taking a walk before dinner, I thought about how we could make this change less disruptive. Perhaps we could disable the check for whether permissions exist unless the migration specifies
validate: true
.Now it is after dinner, and I am reading your comments. I think if we made this validation optional, it would make updating the tests much simpler. I think the trouble you ran into is a sign that the code is too tightly coupled, and adding an option might be the right way to loosen things up a bit.
I am also looking at the existing code, and I see that the
validate
option is defined inEntityConfigBase
, so it is not available inEntityUserRole
. (If we were dealing with a content-entity destination, then I guess we could be changing the entity type’svalidate()
method.) But we can add an option. To avoid confusion, I think we should call itvalidate_permissions
instead of justvalidate
.The disadvantage of this approach is that validation would be disabled by default. If we want to change that, then we can open a follow-up issue to make
validate_permissions
a required option, or change the default. I think that would be simpler than the current issue, but more disruptive, so we would want to target Drupal 10.I am sorry I did not think of this earlier. I guess I should take a walk before dinner more regularly.
I will not do a complete review at this point, but here are a few questions and comments on the latest patch.
You added
$this->migrateContentTypes()
to several tests. I guess that is so that content types are created, which affects the permissions. But it seems as though you added it to a lot of tests. Do they all end up running the user-role migrations and testing the migrated roles? In any event, we should need fewer changes if we add thevalidate_permissions
option.You also enabled
menu_ui
in many of the tests. I guess this is required bymigrateContentTypes()
?Why make the node-type migration required for D6 and optional for D7?
The
@param
comment is good, but this function still needs anarray
declaration:This function needs two type declarations:
And this one:
I remember working on an issue to add permissions for each custom block type. So I think we need to add a dependency on block-type migrations. If I have enough time and energy before bed, I will add another comment with a more comprehensive list.
Comment #39
quietone CreditAttribution: quietone as a volunteer commented@benjifisher, I agree that walks are great for problem solving.
I started reading the comment and stopped at
Oh my, that is a serious mistake and that needs to be fixed before anything else. And it proves my understanding of 'optional' above was wrong. It was correctly behaving as 'required' and I just plain missed it.
This patch is based on #32 and adds the optional dependency on the node_type migration.
Comment #40
benjifisherHere are all the permissions callbacks declared in
core/modules/*/*.permissions.yml
:\Drupal\content_moderation\Permissions::transitionPermissions
Load all the workflows defined using thecontent_moderation
module. Get the transitions for each workflow. Create a permission for each transition.Drupal\Workflows\Transition
is not an entity, and I find nothing when I search for/transition/i
incore/modules/migrations/*
.\Drupal\content_translation\ContentTranslationPermissions::contentPermissions
I think that means we need a dependency on any migration that creates an entity type or a bundle. I do not think there are any migrations that create entity types, but there are several that create bundles:
block_content_type.yml
d6_comment_type.yml
d7_comment_type.yml
d6_language_types.yml
d7_language_types.yml
d6_node_type.yml
d7_node_type.yml
d6_taxonomy_vocabulary.yml
d7_taxonomy_vocabulary.yml
I found most of those by searching for "type", but that missed the vocabulary migrations.
There are 25 config entities in Drupal core modules. (Search for "@ConfigEntityType" in
core/modules/*/src/**
.) I searched the migration YAML files forentity:(block|block_content_type|...)
and found 75 matches. They are all the destination plugin in the migration file.I think that list has a lot of false positives. Not every config entity describes the bundles of some content entity type. If I only count the config entities that include the
bundle_of = ...
annotation, then there are just 7. These are the destination plugins in 11 migrations:block_content_type.yml
d6_comment_type.yml
d7_comment_type.yml
d6_taxonomy_vocabulary_translation.yml
d7_taxonomy_vocabulary_translation.yml
contact_category.yml
d6_node_type.yml
d7_node_type.yml
d7_shortcut_set.yml
d6_taxonomy_vocabulary.yml
d7_taxonomy_vocabulary.yml
That seems like a lot of work to find the 4 that I missed and rule out the two
language_types
migrations.Drupal\field_ui\FieldUiPermissions::fieldPermissions
Drupal\filter\FilterPermissions::permissions
\Drupal\layout_builder\LayoutBuilderOverridesPermissions::permissions
\Drupal\media\MediaPermissions::mediaTypePermissions
\Drupal\node\NodePermissions::nodeTypePermissions
Drupal\rest\RestPermissions::permissions
Drupal\taxonomy\TaxonomyPermissions::permissions
That is all I have time for tonight.
Comment #41
benjifisherWe discussed this issue briefly during #3204288: [meeting] Migrate Meeting 2021-03-25. Using optional dependencies seems like the right thing to do. I still think we should consider adding the
validate_permissions
option as in #38. For now, I am going to finish what I started in #40.Drupal\field_ui\FieldUiPermissions::fieldPermissions
:As I said, I do not think there are any migrations that create entity types.
Drupal\filter\FilterPermissions::permissions
I think we need dependencies on
d6_filter_format
d7_filter_format
\Drupal\layout_builder\LayoutBuilderOverridesPermissions::permissions
: Load allentity_view_display
config entities that are configured for per-entity LB settings. Add two permissions for each. The permission names include "layout overrides", so I do not think any Drupal 6/7 sites would have such permissions. I think we can skip this one.\Drupal\media\MediaPermissions::mediaTypePermissions
:There are no core migrations using the destination plugin
entity:media_type
.\Drupal\node\NodePermissions::nodeTypePermissions
: These are the first permissions we thought of.Drupal\rest\RestPermissions::permissions
: For eachRestResourceConfigInterface
configuration entity, create permissions defined by its resource plugin.There are no migrations that use
entity:rest_resource_config
as the destination plugin, so I think we can skip this one.Drupal\taxonomy\TaxonomyPermissions::permissions
: For each vocabulary, add CRUD permissions for terms in that vocabulary. I think we already handled this under (2) in the previous comment.Combining (and sorting) the lists from (2) and (4), I think these are the migrations we need to add as dependencies:
block_content_type.yml
contact_category.yml
d6_comment_type.yml
d6_filter_format
d6_node_type.yml
d6_taxonomy_vocabulary.yml
d6_taxonomy_vocabulary_translation.yml
d7_comment_type.yml
d7_filter_format
d7_node_type.yml
d7_shortcut_set.yml
d7_taxonomy_vocabulary.yml
d7_taxonomy_vocabulary_translation.yml
Back to NW to add these dependencies.
Comment #42
quietone CreditAttribution: quietone as a volunteer commentedAdded all the optional dependencies identified by benjisher (thanks!), added the validate_permissions option to the destinations updated the tests. Both the d6 and d7 test now have a new test method testUserRoleWithPermission that runs all the optional and required migrations with validate_permissions set to TRUE. I reworked the test quite a bit with the aim of making it easier to see the changes in permissions. I hope I succeeded.
Comment #43
benjifisherI think this issue is getting close to done! Of course, I still have a few small suggestions.
I need to get some sleep, so I did not yet review the D7 stest.
Now that we calculate the list of permissions in the
create()
method, the permission handler can be local to that method. We do not need to make it a class property.I think that
$destinationPermissions
is a better name for this variable:You discussed enabling the
locale
module in #15. Let’s try again to figure out why this makes a difference.Let’s be consistent:
Since we might want to make
validate_permissions
true by default, some time in the future, let’s be consistent by setting it (tofalse
) in both migrations. Besides, this makes it a little more discoverable.Why call the key
lookup_up
?This comment seems out of place now:
Maybe just delete the comment. Maybe replace it with one in the
setUp()
function, explaining that permissions from thefilter_format
table are not migrated whenvalidate_permissions
is set and thefilter_format
migration has not been run.I think this can be simplified:
Why not loop through the messages, testing the message level as you go but then
$actual_messages[$message->src_rid] = $message->message;
? Then, after the loop, assert that the two arrays are the same.Same function: we can use
string[]
instead ofarray
in the@param
comment for$expected_messages
.Same function: the function signature has no type declarations.
This comment seems off point now that we only filter permissions if
validate_permissions
is set:These long lines are hard to read. What do you think of helper functions to merge and diff with
$this->expectedPermissions
? The helper function could also handle translating role names. Or maybe it can just take source and destination roles as arguments.Do we need
shortcut
in the D6 test? My list in #41 includesd7_shortcut_set
but notd6_shortcut_set
.What about
d6_node_type
?Didn’t we deprecate
$migration->set()
? No, I guess I owe you a review on #2796755: [PP-1] Deprecate Migration::set().Comment #44
quietone CreditAttribution: quietone as a volunteer commented@benjifisher, good to know we are getting close. I had intended to comment that I wanted to do a self review but obviously forgot. You picked up quite a few of the things wanted to improve.
1,2,4,8,9 Fixed
5. changed lookup_up to rid.
6. Instead of setup() I put it in the class docs. If I were new to this that is where I would like to find that information.
7. Changes applied to both d6 and d7. And removed $count, there isn't a need to test that anymore.
10. d6_user_role has an existing required dependency on d6_filter_format so this comment is currently true. I guess that should change as well. Adding this to the todo
11. Added a helper to set the expected permissions. I wasn't sure about the formatting just did something I like.
12. copy/paste error. Fixed.
13. d6_node_type is executed in the helper
$this->migrateContentTypes();
14. Yes I was thinking this would be changing in the near future.
TODO 3, 10
Comment #45
quietone CreditAttribution: quietone as a volunteer commented#43.3 Further to needing 'locale' installed on three tests that previously didn't needed it. Poked around a bit and saw that each of those tests installs config_translation which has a dependency on 'locale'. locale also adds a permission. When getting the permission the permissionHandler is looking for the service 'locale.config_manager' which, of course, does not exist and results in a fatal error. Installing 'locale' fixes that.
There are other Kernel migration tests that install config_translation and not locale and they work fine because they are not doing anything that requires the locale.config_manager.
Comment #46
benjifisher@quietone:
Nice work! I especially appreciate the helper
setDestPermissions()
. When I looked at the patch from #42, I did not even realize that thearray_merge()
and thearray_diff()
were adding to/removing from the same arrays. Long lines really are hard to understand.I see you already fixed a lot of minor issues in the D7 test.
And thanks for tracking down the dependency on the
locale
module. That gives me an idea …Let’s initialize
$destinationPermissions = []
and then wrap the middle two lines inif (!empty($configuration['validate_permissions']))
. Then we only generate the list of permissions when we need it. As I said before, it is moderately expensive. And we can remove thelocale
that we added to the existing tests. (I tried one of them.)What do you think of renaming this helper function to
expectPermissions()
?Same function: if we add the default value
$deletions = []
, then we can leave off that argument in 5 of the places it is called.Same function: can we move it to the top of the file, with the other helpers?
I think we can move the
sort()
to thesetUp()
function. We mostly useassertEntity()
with the permission arrays set there. I think there are just two exceptions, and they both start with those permissions and then remove some witharray_diff()
. There was another recent issue where we discussed this:array_diff()
does not change the order.Comment #47
benjifisherI forgot to set the status to NW. While I am at it, let me see if the issue summary needs any attention ... also the change record.
Comment #48
quietone CreditAttribution: quietone as a volunteer commentedAnother installment...
#46
1. Yes, that should done. Fixed. Removed local from MigrateTermNodeTranslationTest, still needed in the block tests.
2. Name changed.
3. 3rd parameter defaults to [] now and 3rd parameter removed from calls as needed.
4. Yes, moved. Oops, it is there because I prefer helpers at the end of the file.
5. Moved the sort but still need one in UserRoleWithPermissions. Still, it is less sorting overall.
Comment #49
quietone CreditAttribution: quietone as a volunteer commentedIt helps to upload the patch.
Comment #50
benjifisherI updated the change record, and I copied the first line of my draft as the release-notes snippet here.
Comment #51
benjifisherI think we want
!empty($configuration['validate_permissions'])
, notempty($instance->destinationPermissions)
:I have a feeling that the test in #48 is going to fail. If we get this straight, then I do not see why we need
locale
in the block tests.Update: I take it back: the tests will not fail, but we will always calculate the permissions, whether we use them or not.
Comment #52
benjifisherThis is what I meant in #46.5: if
$this->sourcePermissions[3]
is already sorted (insetUp()
), then$admin_permissions
does not need to be sorted.Comment #53
quietone CreditAttribution: quietone as a volunteer commentedThis will be very brief as I have an appointment.
#51 What a ditz I am. Fixed now and also the Block tests.
#52. The array_diff preserves the keys which means it will not match. The sorting process fixes that.
Edit: s/not/now
Comment #55
quietone CreditAttribution: quietone as a volunteer commentedInstead of sort use array_values.
Comment #56
benjifisher@quietone:
Do not be too hard on yourself. I often say that even smart people make dumb mistakes. I also usually blame multitasking.
I am glad we can remove the changes to unrelated tests. Such changes are always reason for skepticism.
Good call on using
array_values()
!The patch in #55 fixes the last of my concerns: RTBC.
Comment #58
benjifisherI just added #3207086: [HEAD BROKEN] Consistent failure in MonthDatePluginTest for the unrelated test failure. Let's see if there is any response on that issue before we ask the testbot to reconsider.
If the testbot just switched to DST, then a lot of tests will fail, and we do not want to queue them all up at once.
Comment #59
Spokje#3207086: [HEAD BROKEN] Consistent failure in MonthDatePluginTest was committed. Ordered retest and put this issue back to RTBC per #56.
Comment #61
benjifisherNow a different unrelated test is failing!
I reported the failure at #3207125: [random test failure] SettingsTrayBlockFormTest:: testEditModeEnableDisable(), and I am queuing another re-test.
Comment #62
alexpottI think this should be true for new migrations. Existing migrations will not have this key and therefore will use the existing behaviour but new migrations should benefit from this change today without having to do additional steps.
Existing migrations will have already copied the template and not have the key so I don't think we need to be concerned about them. If someone is starting fresh and doing repeated core upgrades from D6 or D7 while also upgrading there codebase then they are doing that because they want the latest migration bug fixes of which this is one.
Comment #63
alexpottThis bit kinda concerns me though. We're adding these because they create dynamic permissions and so the role migration needs to depend on that. But it's possible for contrib to have dynamic permissions like this to. How is it going to work for such a module if it provides a migration? For example, what's the impact of this change on commerce migrations? I'm pretty sure that set of modules has some dynamic permissions.
Comment #64
alexpottI think in an ideal world permissions would be fixed up after all the migrations have been run and the migration is finished. This would be kinda of what would happen if #2571235: [regression] Roles should depend on objects that are building the granted permissions lands and someone does a migration and then goes to the user permissions screen and presses save. But we don't have the ability to set post migration tasks do we?
Comment #65
benjifisher@alexpott:
Thanks for taking a look!
Re #62:
I agree with changing the default from
false
totrue
, but I do not think we should do that until Drupal 10. This is potentially disruptive: not only may it affect current migrations, but an experienced migrator might start a new project and expect things to work as they always have. We have already made BC breaks in the migration system, like the one fixed by #3184650: ContentEntity migration source adds revision ID as source key, incompatible with Drupal 8.8 and earlier.That applies to projects that use config entities to represent migrations, supported by the
migrate_plus
module. That is not the only way to run migrations. There is the core UI, themigrate_run
module, and Drush 10.4.When I start a new project, I like to target the version of Drupal that will be current when the project is ready to launch. If I were to start one today, then I would use 9.2.x. There are lots of reasons I might want to update that are unrelated to migrations.
Re #63:
I think we have done everything we can in core to handle the dependencies. A contrib module that provides migrations will probably need to add dependencies.
What can we do to make this easier for the maintainers? We should probably add some code samples to the change record. Anything else?
This seems like another good reason to keep the BC default until Drupal 10.
Luckily, one of the maintainers of the
commerce_migrate
module is already involved with this issue!Re #64:
It depends on what you mean by "post migration". We have
MigrateEvents::POST_IMPORT
: see the change record. I do not think that has any advantage over changing the destination plugin, which is what we do here.If you mean when the migrations are "done done" and the site is ready to go live, then there is no way to tell. Incremental migrations can be done even after the site goes live.
To recap:
If the current patch is committed, then it will be easy to change the default in a later issue. The hard part will be the documentation.
If I still have not convinced you, then I am willing to go along with changing the default now.
Comment #66
benjifisherAnother related issue: #2571235: [regression] Roles should depend on objects that are building the granted permissions.
Comment #67
alexpottYeah that doesn't offer any advantage. We need is something that will run once a complete set of migrations is finished. What I think would work best is if there was some role cleanup migration that always depends on all the existing migrations - this step will remove any permissions which doesn't exist at this point. So I think we leave the existing role migration as it is but somehow introduce another migration whose sole purpose is to fix the permissions and run after all other migrations have finished.
However I don't think we can do this until we've landed #2571235: [regression] Roles should depend on objects that are building the granted permissions - since that'll mean all this step will need to do is to re-save the role without the
migrating
flag. While is #2571235 is outstanding and we're dealing with D6 and D7 data I think attempts to make this stricter are likely to run into hard to predict edge cases. If you look at user_role_change_permissions() works and \Drupal\user\Form\UserPermissionsForm::submitForm() it's like the system is designed to work with permissions that are not declared to the system.Comment #68
benjifisherFrom #67:
That is what I was thinking of when I said (in #65), "... there is no way to tell. Incremental migrations can be done even after the site goes live."
I just read a description of an upcoming DrupalCon session. It talked about how bad content keeps getting migrated from one template to another, and no one ever cleans it up. The same thing happens with permissions. At least, this issue (in its current form) gives the developer working on the migration the option to break that cycle.
Perhaps we should
Comment #69
benjifisherAs I suggested in #68, I am demoting this issue from Critical to Normal. I am also cleaning up the issue tags.
I updated the issue summary and the change record, mentioning that we log messages when removing permissions.
@alexpott, it seems to me that what you are asking for is a different, but related, feature. I do not see that as a reason to postpone this issue. We can reopen #3055557: Role migration results in migration permissions that do not exist on the target site or open a new issue to do what you want, postponed on #2571235: [regression] Roles should depend on objects that are building the granted permissions if that is appropriate.
I still do not see how that is supposed to work. We can discuss that on another issue, or at one of the weekly migration meetings. I just added a note to #3207879: [meeting] Migrate Meeting 2021-04-22.
I am not especially worried about the edge cases. The new option is opt-in, and we log a message for any role that has permissions removed. Let's give developers this option and let them tell us what the edge cases are!
Comment #70
benjifisherI finally noticed that #67 refers to "the
migrating
flag." What flag is that? I see that it is added as part of #2571235: [regression] Roles should depend on objects that are building the granted permissions. I think I will leave a comment on that issue.Comment #71
alexpottAs stated in #67 I don't think this is rtbc. The solution of adding dependencies to the roles migrations doesn't work because core doesn't yet enforce any sort of dependencies on permissions - they don't even have to exist in permissions.yml or a callback in order to be used! So it's perfectly possible for a role have a permissions and it to be checked in code and for the permission to not be declared. We have tests in HEAD that rely on this behaviour - see #2571235: [regression] Roles should depend on objects that are building the granted permissions. I think we need another approach. I think perhaps the way forward is to use a special configuration for migrate that stores permissions that do not exist at the time of role migration. These permissions are declared as real permissions by the user module under a special migrated permission section. As we go through migrations we have MigrateEvents::POST_IMPORT event that cleans up these permissions as they come to exist. If we combine that approach with #2571235: [regression] Roles should depend on objects that are building the granted permissions think I think we'll get to a place where we can deprecate support for permissions that don't exist and solve the critical bugs that exist because we don't tidy up after ourselves.
Comment #72
benjifisherNow that I have looked at #2571235: [regression] Roles should depend on objects that are building the granted permissions, I think the right action is to postpone this issue until that one is fixed.
I am doing that and also restoring the Critical status of this issue. I will also make some updates to the issue summary.
Comment #73
quietone CreditAttribution: quietone as a volunteer commentedPossibly related? #2969282: Error UserPermissionsForm
Comment #75
benjifisher#2571235: [regression] Roles should depend on objects that are building the granted permissions has landed, so we can return to this issue.
Given the changes in #2571235, we will have to reconsider the proposed resolution, so I am adding the tag for an IS update.
Comment #76
benjifisherComment #77
quietone CreditAttribution: quietone as a volunteer commentedRestarting by making a test only patch from the latest patch, including fixing a typo. No need to run tests right now.
Comment #78
mikelutzAt the core level, we really only need to worry about this for the migrate_drupal_ui module. Such a cleanup can be run at the end of the batch process in that module. Running incremental migrations, or migrations one at time or with drush, or other developer-y things that developers do to migrate a site in the real world end up being out of our control, and there are infinite ways to use the migration system to do bad things if you have developer access.
Comment #79
quietone CreditAttribution: quietone as a volunteer commentedThere are also 'Follow-up Migrations'
Comment #81
quietone CreditAttribution: quietone as a volunteer commentedI am not sure how to proceed on this.
For the Migrate UI, do we want to only migrate roles that exist on a row by row basis or migrate all the roles and clean the up at the end.
For those migrating with other tools. What support is needed? Will they need a way to remove migrated roles if they are migrates as per #71.
Comment #82
danflanagan8This is an interesting question! I was looking at the core issue that added dependencies to roles (#2571235: [regression] Roles should depend on objects that are building the granted permissions) to see how they handled cleaning up existing roles. They have a post_update function that does all the cleanup:
https://git.drupalcode.org/project/drupal/-/blob/9.4.x/core/modules/user...
To me it seems like from the migrate perspective, we just need to come up with a way to call that function (or a copy of it) after the migration is complete. Sounds like @mikelutz things we can put that at the end of the batch that migrate_drupal_ui uses.
From a contrib standpoint, maybe migrate-tools gets a new drush command to call the update?
Comment #83
quietone CreditAttribution: quietone as a volunteer commentedI am not convinced of that. If the permission is not migrated in the first place then there is no need for cleanup. That is what the destination plugin does in #55, when validate_permissions is enabled on the destination.
Comment #84
mikelutzRight, but the issue that @alexpott brought up is that #55 makes the D7_user_role migration dependent on any core migrations that may create dynamic permissions, but it can't do that for contrib modules that might have migrations that create dynamic permissions. So if we go with #55 and user_role runs before products for example, it will strip out per product permissions because they don't yet exists. (This is an off the top of my head example, I don't recall the permission structure of commerce, but the idea is the same in general.) Since we can't make D7_user_role depend on any random contrib migrations that would have to run first to create dynamic permissions, the only choice we have is to migrate all the permissions and then clean up when the whole upgrade process is done.
Comment #85
quietone CreditAttribution: quietone at PreviousNext commented@mikelutz, thanks. that all makes sense.
Here is the start of a new patch, working to implement #67, where the permissions that do not exist at the time of the user_role migration are created as temporary permissions. When a migration is done via /upgrade these temporary permissions are removed and the roles updated when the migration is complete. The roles are updated using an exact copy of user_post_update_update_roles(). When a migration is run by other means the temporary permissions are not removed but since they are stored in config they can easily be accessed and removed.
So, is this now moving in the right direction?
No interdiff because of the new approach.
Comment #86
quietone CreditAttribution: quietone at PreviousNext commentedoops, I wasn't working from HEAD
Comment #88
quietone CreditAttribution: quietone at PreviousNext commentedInstead of adding more changes, this moves the abstract method declaration. However, it is inconsistent with the way the getEntityCounts() is defined but this should allow the tests to pass. It can be changed later to be consistent if that is what we want.
Comment #89
quietone CreditAttribution: quietone at PreviousNext commentedNeeded a reroll and moving to D10
Comment #91
quietone CreditAttribution: quietone at PreviousNext commentedAnd update the new Upgrade tests in aggregator.
Comment #92
benjifisherThis is an interesting approach. I was not thinking that the Migrate module would actually define (or provide) permissions, but I guess that is what we have to do.
I think we should expand the "Proposed resolution" section in the issue summary. Be more explicit about how we add permissions and how we remove them, and how the user roles get updated. I can do that, but not at this hour. I see this issue already has the tag for an issue summary update. +1
migrate_drupal_ui
, but it could happen if an error interrupts the process before the cleanup stage. At least, I think that could happen. And it is certainly a possibility for custom migrations. So let's implementhook_requirements()
and add a warning to the site status report if any temporary permissions are defined in the new config object.There is a simpler way. Make all of the temporary permissions depend on the new config object. See Permissions can define dependencies, one of the change records for #2571235: [regression] Roles should depend on objects that are building the granted permissions. We do not need the new trait mentioned there. Just add the dependencies in
nonExistentPermissions()
. Then, whenever the config object is updated, the config dependency system will take care of updating the roles. (I am pretty sure that is true. We need to confirm!)This also makes it really easy for a contrib or custom module to clean up the permissions: just delete the temporary permissions.
Actually, the permissions are already listed on
/admin/people/permissions/module/migrate
. Would it make sense to add a link there to a confirmation form?updateRoles()
. We might still want it, in order to provide logging. If so, and if I am right about dependencies, then we have to call the function before updating the config. But if this method is really an exact copy ofuser_post_update_update_roles()
, can we just call that function instead? Maybe not: I guess that update functions are eventually removed.migrate
module automatically. If you follow my earlier suggestion, then the role should also depend on the config item. If themigrate
module is uninstalled, or if the permission is removed from the config object, then the role should be updated (removing the permission).I think all of that is pretty good DX.
I would like to see test coverage of all of that. If we test removing a role from the config object, then maybe we do not also have to test uninstalling the module.
Nit: Can we add "comma separated" to the
@return
comments?The brackets are optional in
[s]?
. If you think they make the regexp easier to read, then it is worth keeping them. I think we need to group the alternation:(have|has)
. I am nervous about inserting a variable into a regular expression. I guess it is OK, since it is a comma-separated list of machine names. Can we just applypreg_quote()
to be safe?Naming things is always hard. I will not insist on any of these changes. They are just suggestions.
getTemporaryRoles()
in the tests instead ofgetRoles()
.migrate.permission
config item with the keypermissions
, I would usemigrate.settings
with the keytemporary_permissions
.removeTemporaryPermissions()
is clearer thanupdateRoles()
.Comment #93
benjifisher#92.3: I guess the simplest thing to do is add links to the warning message (on the site status report) to the Migrate permissions page and to a confirmation form. Other than the confirmation form, we do not need a new page.
In the new destination plugin, it is a little confusing to use the same variable for a string and then for an array:
The first two times I looked at this, I thought we were passing a string as the second argument of
array_merge()
.Maybe just eliminate the first variable:
or something similar with
sprintf()
.Comment #94
quietone CreditAttribution: quietone at PreviousNext commented@benjifisher, thanks for the review and the renaming suggestions. I recall not liking the names at the time but I was focused on the functionality.
It is late and I only worked on the simpler points and the renaming.
#92. 6, 7 and 8 Fixed. Also all the renaming points in the final paragraph.
TODO. #92 1 -> 5 and 9. # 93
Comment #95
benjifisher@quietone, thanks for keeping this moving!
By the way, having a patch to review is a huge help. In principle, I could have made those suggestions before you did any work, but having a patch to review gives me a lot more focus.
After sleeping on it, it occurs to me that we are not thinking about the iterative nature of migration development. We work on the migrations and the site building in parallel. For this issue, that means we have to consider what happens when we migrate some user roles, then enable one or two modules that define "missing" or "temporary" permissions, then repeat the process.
What happens when two modules define the same permission? I do not know. The permission machine names are the array keys of
$permission_handler->getPermissions()
, so that function will return only one of them.I was thinking that the destination plugin should recalculate the temporary permissions from scratch, instead of just adding to them. But I was wrong: the destination plugin is looking at just one role at a time. It could check the existing temporary permissions, and remove the ones that are defined by another module. But maybe it does not have to: see the next point.
I think what we have to do is react whenever a module is enabled. There is a hook for that, isn't there? Or maybe an event. Check the permissions it defines, including its permissions callback. Because of the array-key issue I mentioned before, we cannot rely on
$permission_handler->getPermissions()
. If any of those permissions are defined by the Migrate module, then remove them.Comment #96
quietone CreditAttribution: quietone at PreviousNext commentedThat it true. mikelutz provides an answer in #78 which is that we only have to handle the case of /upgrade. And, right now, I that is correct for the scope of this issue. Exploring implications with iterative nature of migration can be in a separate issue? However, I just realized that there is nothing in the current patch that checks what happens to the temporary permissions when the user role migration is rolled back. Added that to the todo list at the end of this comment.
Ah, I stumbled on this while working on this issue. I reckon this needs an issue, if there isn't one already.
This patch adds information to status report page (#92.1), which will need work. It should at least link to somewhere where the user can learn how to remove the permissions. Also, it removes the check for skipping the missing permission deprecation in Entity/Role.php that was accidentally left in the previous patch.
TODO. #92 1 -> 5 and 9. # 93, test rollback
Comment #98
quietone CreditAttribution: quietone at PreviousNext commented#92. 2 and 9. The destination plugin EntityUserRole now adds a title, description and dependency on 'migrate' to the stored permissions. This required a few tweaks in the tests and an expanded schema file. The description can and does include the migration id as can be seen in this screenshot of /admin/people/permissions/module/migrate. Since it is very likely that there is only one migration of user roles, it doesn't seem very useful to me. Viewing the permissions is not yet available with this patch. I did it by running a Drupal 6 upgrade with some code commented out so the permissions were not removed.
I did uninstall migrate and only some of the permissions were removed but I need to retest that.
Comment #99
quietone CreditAttribution: quietone at PreviousNext commentedWhen I uninstalled migrate I got the confirmation screen about what config was to be updated and when I uninstalled all the migrate permissions were removed. So, that is good. I also noted that on the module page that migrate had a permission link when there were permissions.
But I found another thing to look into, when there are no migrate permission navigating to /admin/people/permissions/module/migrate results in a redirect loop.
And thinking about a form to allow deleting the temporary permissions, right now I think that would be better in a followup.
Did some work on the IS so removing tag.
Comment #101
quietone CreditAttribution: quietone at PreviousNext commentedI forgot to update the status page test due to the changes to the config schema.
Comment #102
benjifisherWhat is supposed to happen is a 403 response, and the link should not appear on
/admin/modules
.I tested this, and it seems to work as designed. I applied the patch from #101 and installed the Standard profile. The "Migrate permissions page" means
/admin/people/permissions/module/migrate
.drush config:import --partial --source=config
to import the YAML file below, saved asconfig/migrate.settings.yml
.drush config:edit migrate.settings
to settemporary_permissions: []
.I was afraid that the access result was cached incorrectly, so I did not clear caches during this test.
Can you give steps to reproduce the redirect loop?
Here is my YAML file:
Comment #103
benjifisherI did a quick review of the changes inside the
migrate
module. It looks good so far! I just have a few comments and really minor suggestions.t()
andformatPlural()
, but procedural code only has the first. Oh, well.t()
? Or, as I suggested in #93, we do not have to work so hard here. Since the permissions are already listed on the Migrate permissions page, we can give a link to that instead of generating the list here.Comment #104
benjifisherI looked at the changes inside the
user
module. Again, just a few minor points.$storage
and$bundles
properties, and these have to be supplied by the constructor. I would expect those to be in EntityContentBase. Can config entity types have bundles?$temporary_permissions = [];
at the top of the function, outside theif()
block, so that it is always defined when you get to the last block of the function.$provided_by_migrate
is defined and then never used. Am I missing something?Edit: to answer my own question, yes, config entities can have bundles. So I guess it does make sense for the Entity class to have those properties.
Comment #105
benjifisherI looked at the remaining changes: in the
aggregator
andmigrate_drupal_ui
modules. I think there are a couple of things that should be changed.I know this is a direct copy of the post-update function, but I think we let a goof slip through:
The last three lines should be inside the
if
block. If there are no removed permissions, then there is no need to update the role.You missed one doc block that can use "comma separated":
If the method returns
''
, then there is no need tooverride the base implementation.
There are still a couple of to-dos from #92, right? If you want to leave the cleanup form to a follow-up issue, then I think I can implement it.
Comment #106
ravi.shankar CreditAttribution: ravi.shankar at OpenSense Labs commentedAddressed point 1 and 3 of comment #105, still needs work for others.
Comment #107
benjifisher@ravi.shankar:
Thanks for helping with those points. Are you planning to continue working on this issue?
Comment #108
quietone CreditAttribution: quietone at PreviousNext commentedI went through all the remaining points to determine what is still to do. I wanted to make a summary but I don't have the brain power for that now. I should be able to have another look at this in the next two days.
Updated patch with some fixes.
#92
1. Fixed, hook_requirements is in migrate.install
2. Fixed in #98
3. I agree that a UI is needed for deleting the temporary permissions. Creating such things is a weak point for me but adding a button to the migrate permission page seems wrong and inconsistent. I reckon a new form is needed.
4. Todo
5. Todo
9. Fixed in #98
93. Removed $temporay_permission variable that was used for the building of the log message.
#102. Todo Provide steps to reproduce the redirect loop
#103
1. Oh well indeed.
2. Todo
3. Fixed, no longer using editable config.
4. I did add the dependencies in the callback at first. But that split up writing to the config in two places and it seems
cleaner to do it in one place, the destination plugin.
5. Fixed permission callback is one liner.
#104
1. It does seem a bit odd, the bundle is the same name as the config entity.
2. No change needed due to #105.1.
3. Ah, that is dead code. The real test is at the end of the file, after the comment "Confirm that temporary permissions are stored.".
#105
1. Fixed in #106
2. Fixed.
3. Fixed in #106
todo: test rollback
Comment #110
yogeshmpawarLooks like test failures are unrelated.
Comment #111
quietone CreditAttribution: quietone at PreviousNext commented@yogeshmpawar, thanks for confirming the failure was a random fail. If you restarted the tests, I think we can save resources and not rerun the tests on every failure. There is lots more work to be done here anyway.
This does #103.2, removes the inline template in hook_requirements. The message on the status report page could use improvement and now looks like this:
List of remaining items
#92. 4, 5
102. Provide steps to reproduce the redirect loop
test rollback
UI to remove temporary permission
Comment #113
quietone CreditAttribution: quietone at PreviousNext commentedAnother installment.
test rollback - The destination simply removes all the temporary permission.
92. 5 - Changes StatusTest to PermissionTest and, at the end, uninstalled migrate and confirmed the temporary permissions are gone.
List of remaining items
#92. 4
102. Provide steps to reproduce the redirect loop
UI to remove temporary permission
Comment #115
quietone CreditAttribution: quietone at PreviousNext commentedAnother random failure: Drupal\Tests\media_library\FunctionalJavascript\CKEditorIntegrationTest::testButton
And now for the remaining issues.
#92-4. MigrateUpgradeImportBatch::removeTemporaryPermissions. Yes, this is definitely needed for logging. I also think that this should
stay so that each role has the temporaryPermissions removed at the end of the upgrade. I fixed the method so it is now comparing with the temporary permission and removing said permission after the roles are updated.
102. Provide steps to reproduce the redirect loop. I am not able to reproduce this so perhaps just a wonky test site. This is not for this issue but when there are no permissions I get an access denied message instead of a some nice message that there are no permissions.
And the last remaining task is to provide a UI to remove the temporary permissions. I still think that is outside the scope of this issue. And, if the tests are correct, we know that the temporary permissions will be removed when migrate is uninstalled.
Hopefully, all the points have had a response and we are all caught up.
And ready for the next round!
Comment #117
benjifisher@quietone:
Yay, progress!
That is by design.
The access checker means that we do not generate links to empty permissions forms. For example,
/admin/modules
already checks access for the Configure links; as of Drupal 9.3, it does the same for the Permissions links.If you can think of a better way, let's talk about it ... on another issue. (As you said, "... not for this issue".)
If that were a separate issue, then we would close it as "cannot reproduce".
I am willing to implement that in a follow-up issue.
I will do a full review in a couple of days. I already looked at the interdiff in #108, and that much looked reasonable.
Comment #118
quietone CreditAttribution: quietone at PreviousNext commented@benjifisher, thanks. Yes, progress indeed, I am beginning to think the end is in sight!
Thanks for the explanation about the access checker. I am content to live with it as is. And ehile I do enjoy closing issues, after experience with Bug Smash I won't be making a new issue. ;-)
Adding tag for the followup.
Now for this patch. The test failed because temporary_permissions was expected to be an array and it was NULL. So, this add a config/install so that the temporary_permissions are initialized to an empty array. And I got a bit defensive and ensured that $temporary_permissions is never NULL by using '??'. This patch also adds some comments.
What is to do is to improve the requirements message. I have updated the remaining tasks.
Comment #119
benjifisherI added #3272352: Add a UI for deleting temporary permissions as a child issue, and assigned it to myself.
I think we should update the tile of this issue.
Comment #120
benjifisherOh, no! I must have forgotten to force-reload this page. I did not intend to make all those changes in the issue summary.
I am doing my best to restore the IS.
Comment #122
quietone CreditAttribution: quietone at PreviousNext commentedThis time the fail is Drupal\Tests\ckeditor5\FunctionalJavascript\MediaLibraryTest::testButton
I've added more tweaks to the IS but am blank on a new title, at least, right now.
Comment #123
benjifisher@quietone:
I think the patch here needs a reroll after #3264120: Remove aggregator module and our dependency on Laminas Feed.
Since you worked on that issue, I trust that you know whether we can just drop the changes for the tests in the Aggregator module or if those tests have moved somewhere else. Is there now a contrib Aggregator module that will need those changes?
Comment #124
benjifisherMy bad: my local branch still had the changes to the Aggregator module, but it looks as though the patch in #118 already removed those changes.
Comment #125
benjifisherI think we should also have an update function, or maybe post-update, to create the configuration object.
If you change the key to
temporary_permissions
(plural) is it stillNULL
? I would think that the PHP object$config
is not changed when the module is uninstalled.I think the functional test will be even better if we
I think this comment was copied from the post-update function. We are not calculating dependencies.
In fact, I think we can replace
removeTemporaryPermissions()
with a function that returnsstring[][]
. The outer keys are role names, and the values are lists of removed permissions. Then, instead ofwe can do something like
(The logger channel should be
'migrate'
, right?) See #92.4.I think we have to worry about the edge case where there is more than one migration that uses the
entity:role
destination. Maybe one migration creates roles and then another one updates them.Suppose
foo_migration
adds thefoo
permission to one role andbar_migration
adds thebar
permission to another role. Then we roll back just one of the migrations. Themigrate
module still has to provide the other permission.I think we have to do something like this:
parent::rollback(...)
Maybe there is a simpler way.
I still think that we have to remove temporary migrations when another module that provides them is enabled. I guess this can be done in a follow-up issue if we want to limit the scope of this issue to migrations using the core UI. See #95.
Comment #126
quietone CreditAttribution: quietone at PreviousNext commented@benjifisher, thanks for prompt reply. I didn't get to this till late so kept to simple things and, hopefully, having done anything daft.
1. todo
2. Typo fixed and gets a new $config object for testing.
3. Ah, yes missed this test item. I played around with this and found that the dependency on migrate.settings is set in the user role config. However, when a permission is removed from migrate.settings it is not updated.
4. Copy/paste error fixed.
5. If this change is made then we are looping through all the roles twice. Once in the new temporaryPermissionsByRole method and then again in the ConfigUpdater method. That seems inefficient. Also, should a method for cleaning the roles be put somewhere else and be available to custom code?
6. Or worse, if more than one migration adds unique temporary permissions to the same role there is no way to determine which permission was added by which migration. I don't see a way to handle all possibilities, We can only cleanup at the end. With that in mind I have removed all the rollback code.
7. todo
Comment #127
yogeshmpawarResolved CSpell error & added an interdiff.
Comment #128
benjifisher@yogeshmpawar:
Thanks for removing the unused
use
statement.@quietone:
Re #126.5: I was thinking that we would not need ConfigUpdater at all. Deleting permissions from the config object should have the effect of updating the roles and removing the temporary permissions.
According to #126.3, I am wrong about that.
I will do some manual testing, but I might not have time before today's migration meeting.
Comment #130
yogeshmpawarUpdated patch will fix test failure.
Comment #132
yogeshmpawarone more try to fix test failures.
Comment #133
benjifisherI sometimes forget about the "T" in RTBC. I did some manual testing, focusing on the config dependency.
The attached test_user_role.yml is a copy of
d7_user_role.yml
that uses theembedded_data
source plugin to create two roles, with two permissions each.The hard part was installing drush, thanks to #3264918: Update symfony/console to Symfony 6. See Remove DrushArgvInput (PR for drush).
Testing steps
drush si standard
anddrush en migrate
.drush mim test_user_role
.drush mmsg test_user_role
. Results:/admin/reports/status
. Find a warning with a link to .../admin/people/permissions/module/migrate
: roles and permissions are created as expected./admin/config/development/configuration/single/export
, select Configuration type: Role and one of the new roles. It has dependencies on themigrate
module and themigrate.settings
configuration./admin/config/development/configuration/single/import
. Submit the form.We have a problem here. I will have to figure out what it takes to get the user roles to update whenever the Migrate settings change.
Comment #134
benjifisherI realize that I have been making two mistakes regarding the config dependency system:
My hope that updating migrate.settings would trigger removing permissions from roles was wrong for both reasons. Updating migrate.settings is not enough because of (1). And migrate.settings is an object of type Drupal\Core\Config\ImmutableConfig, which does not implement Drupal\Core\Entity\EntityInterface. For example, that interface declares
getConfigDependencyKey()
.I often use "config object" and "config entity" interchangeably. That is a mistake.
I was trying to figure out why deleting the Article content type triggers an updated of the Content Editor role, removing related permissions, but removing migrate.settings did not do the same thing. The reason is (2).
I still think that this issue should take advantage of the config dependency system. That is more complicated than the already implemented system based on migrate.settings, so I have to give some good reasons.
As I said in #128, we should not need to use ConfigUpdater at all if we rely on the config dependency system. So we save a little complexity there.
To be clear, I am suggesting that the Migrate module define a config entity type, say Drupal\migrate\Entity\TemporaryPermission. Create one of these config entities for each temporary permission.
We decided not to support rollbacks in this issue. That is OK, since the scope of this issue is to support
migrate_drupal_ui
. But the solution we create here has to allow a follow-up issue to support rollbacks. I think the more granular structure (one config entity per temporary permission) will make that easier. We might want to add additional properties to the config entity in a follow-up issue, so that the entity saves the information we need to react to a rollback.We already have one follow-up (child) issue to delete temporary permissions. We also have to delete temporary permissions when another module implements the permanent permission with the same name. That might be in this issue or in a follow-up. I think this is the real reason I want to make use of the config dependency system: there are several situations where we want to delete temporary permissions, and I do not want to use ConfigUpdater each time.
Comment #135
benjifisherI am setting the status to NW for #134 and for an issue summary update.
Now that this issue targets the 10.0.x branch, I think we should update the issue summary. (We should also update the title, as I said before.) I checked out the 10.0.x branch and repeated the first two testing steps from #133:
We should mention this in the issue summary. I am pretty sure that we will get the same result if we try to migrate a permission depending on the Blog content type if that type has not yet been created. That is likely to happen with
migrate_drupal_ui
, so it is in scope for this issue.Comment #136
quietone CreditAttribution: quietone at PreviousNext commentedThis converts the storage of temporary permissions from a config object to a config entity. I am not able to add a comment right now but it is worth seeing if this breaks anything.
Comment #137
quietone CreditAttribution: quietone at PreviousNext commentedForgot to upload the patch.
Comment #139
quietone CreditAttribution: quietone at PreviousNext commentedThe failing tests are not from any tests changed in the patch. There are failures because this patch adds a new configentity.
#133. On 10.0.x I manually tested using these steps, well sort of. Instead of the UI I used drush to delete 'foo test permission', the role was updated correctly. I did do a `drush mr test_user_role` and that caused a fatal on admin/people/permissions/module/migrate because the config entity was deleted. Adding as task for that to the remaining tasks.
#134. The implementation of the configentity was a learning experience so I may have not done things in the correct way.
Since we are not using the configupdated the batch doesn't know which roles were changed. Therefore, the displayed message now shows the permissions that were not migrated which gives the user something to work with.
The Upgrade tests now have to add an entity count for temporary_permissions, which will effect any contrib test that uses the base class. Should be a small number, most contrib don't write a functional test. At least, the last time I checked about a year ago.
Hopefully, the changes to the IS are accepted so removing tag. Title change as well. Adding Bug Smash tag.
Comment #140
benjifisher@quietone:
Thanks for the update!
I looked at the interdiff about a week ago, and it looks good. I owe you a closer look.
I did some testing, similar to the steps in #133. I ran into some problems. The numbering below does not correspond to the steps in #133.
drush cget 'migrate.temporary_permission.bar test permission'
./admin/people/permissions/module/migrate
) you will not see them, but if you look at the role configuration, the phantom permission is still there. I still get a WSOD when I submit the form on the Migrate permissions page.getPermissions()
, I think that part of the answer is that a static permission (from a.permissions.yml
file) always "wins" over a dynamic permission (from a callback). If two modules define the same permission from callbacks, then I think it depends on the order (alphabetical?) of the modules. Based on this, I have to reverse the request I made earlier for the status report. A link to the Migrate permissions page is not good enough, since it might not show all the temporary permissions. Can we go back to listing them explicitly in the warning message?Comment #141
benjifisherI looked into the test failures. Searching on the error message led me to #3104189: entity type needs to be installed - Cache, Request Dispatcher, Request Handler, which refers to search_api_solr_update_8331(). That looks like a model we could follow here.
The change record Write update functions for entity schema updates, automation removed is relevant, but does not have any examples of adding new config entity types. The documentation page Creating a configuration entity type does not mention update functions. We should add a section to it once we get this working.
Comment #142
quietone CreditAttribution: quietone at PreviousNext commented140.2 I meant to come back to fixing the names, thanks for the reminder. The only change in this patch is to add an 'id' field that is a machine name of the permission.
edit: fix grammar and typo.
Comment #144
quietone CreditAttribution: quietone at PreviousNext commentedThe test failures are the same as in #137 plus a random failure, therefor setting to NR. I should be able to work on the other points tomorrow.
Comment #145
benjifisherFor comparison, here are the permissions defined by the Filter module after installing the Standard profile:
Comment #146
quietone CreditAttribution: quietone at PreviousNext commentedAnother increment. Yes, I too looked at the filter permissions for comparison, thanks.
#140-1 and 3. This patch is intended to fix the problem with the dependency. As suggested in #140-1, the dependency is added in the migrate permissions callback. With that in place I found that PermissionTest was wrong because it never actually saved the role when assigning a temporary permission. Oops! That is now fixed and also added a check that the intended checkbox it ticked on admin/people/permissions page for the temporary permission. Another change is that the schema use 'title' instead of 'permission' because the PermissionHandler sorts permissions on 'title'. That of course, required changes to several files. With a bonus that TemporaryPermission is just annotation.
Comment #148
quietone CreditAttribution: quietone at PreviousNext commentedThe failing tests are the same ones.
#140-4. This adds back the list of temporary permissions to the Status Page and thus some adjustments to the PermissionTest. And here is a screenshot of the status page with two temporary permissions.
Comment #150
benjifisherI am having a quick look at the interdiff from #146.
->get('title')
equivalent to->label()
?$temporary_permissions
.array_map()
instead offoreach()
. DoesloadMultiple()
return an array with the right keys?+1 for the updates to the test.
Comment #151
quietone CreditAttribution: quietone at PreviousNext commented#150
1- Changed the temporary config entity definition to use 'label'.
2-3. Yes. This patch uses array_map instead of the foreach loops. The array returned by loadMultiple is keyed by the id (machine name) of the entity, but the PermissionHandler expects that to be the 'label'. For that foreach loop I added a function to build the array.
This patch also should take care of the test failure from Rest and JSON:API. That required adding a 'administer temporary permission' to migrate.permissions.yml and adding several test files. There were plenty of examples in core of the test files that were needed so there was a lot of copy/paste.
All going well, that leaves the update related fixes to do.
Comment #153
quietone CreditAttribution: quietone at PreviousNext commentedAnd now a start on the update tests. I used hook_update_N. The hook starts by installing the new config entity. Then it will search all roles for permissions that are not valid. For those that are not valid a temporary_permission entity is created. This is assuming that all non-valid permissions are from migrate. Testing of the update is added as well. It adds the same two permissions to anonymous and administrator and checks that the new entities are created. This still feels a bit rough, but something is better than nothing.
Locally, the failing test passes so that is a good sign.
Comment #154
benjifisherI did manual testing again. Things have changed since #133, so here are my current steps.
Testing steps
Install Drupal and enable the Migrate module:
drush si standard
anddrush en migrate
.Run the migration:
drush mim test_user_role
.Check messages:
drush mmsg test_user_role
. Results:foo test permission’ not found.
bar test permission’ not found.
Check the status report,
/admin/reports/status
. Find a warning with a list of the four temporary poermissions.Check
/admin/people/permissions/module/migrate
: roles and permissions are created as expected.Check dependencies. On
/admin/config/development/configuration/single/export
, select Configuration type: Role and one of the new roles. It has dependencies on the migrate module and the two configuration entities.Check the config entities. On the same page, select Configuration type: "Temporary permission" and "foo test permission". It does not have a dependency on the
migrate
module. What will happen when I uninstall the module?Delete one of the config entities: use
drush php
to get an interactive shell, thenDrupal::entityTypeManager()->getStorage('temporary_permission')->load('foo_test_permission')->delete();
Return to the Migrate permissions page (Step 5). The deleted permission does not appear.
Repeat Step 6. The deleted config entity is not listed as a dependency, and the corresponding permision is gone.
Go back to the Migrate permissions page and submit the form. I get a standard confirmation message.
Uninstall and re-install the Migrate module:
drush pmu migrate
anddrush en migrate
. The config entities and related permissions are removed, and they are not re-created. The roles remain, without permissions nor dependencies.My first attempt at (8) did not work:
drush cdel migrate.temporary_permission.foo_test_permission
. It looks as though this deletes the config object, not the config entity.The question in (7) is answered in (12).
Comment #155
benjifisher@quietone:
Great work! Not only do you get the
json_api
andrest
tests to pass, but you added more. I confess, I did not look too closely at the new tests.The first two points are a little long, but all them fall into the category of clean-up:
This will not give the correct link on a multilingual site, or if Drupal is installed in a subdirectory of the web root:
If you need a link, use
Url::fromRoute()
. But I am not sure that a link is useful here. On the Migrate permissions form, we can add permissions to a role or remove them, but we cannot delete the permissions. Also, if another module creates the permission, then it might not appear at all on the Migrate page. We can come back to this in #3272352: Add a UI for deleting temporary permissions.What is the use case for this?
I guess this update function will run if the Migrate module is enabled in Drupal 9 and the site is upgraded to Drupal 10. So it might be a site under development (upgrading from D7 to D9-oh-no-it-has-to-be-D10). Or it might be a site that uses the Migrate API for continuing imports and also has some "phantom permissions" (migrated from D7 or left over from deleted config and uninstalled modules before Drupal 9.3).
But we do not support upgrading from Drupal 9.2 to 10. That means
user_post_update_update_roles()
has already run. So most of those phantom permissions were already cleaned up. There still might be some created by migrations. Do we want to convert those to temporary permissions?We have already decided that the scope of this issue is to support UI migrations (or
migrate_drupal_ui
). I do not think we need to support D7-to-D9 in the UI, followed by upgrading to D10, followed by enabling modules and creating config that provide the temporary permissions. Besides, there is no way in the scope of this issue to delete the temporary permissions.Given the scope of this issue, I think we can simplify: remove this part of the function.
I think we should make it plural: "administer temporary permissions". The description is optional, and I think we do not need it. Maybe, when we add a way to delete them, we can add a description.
In #151, you said that we need a permission to get the tests to pass. Does it have to be a permission defined by this module, or can it be an existing permission, like
administer roles and permissions
from theuser
module?That is not what you meant to write, is it? ;)
Instead of hard-coding
'config'
and'migrate.temporary_permission.'
, I would follow the example of the other permissions callbacks and use[$temporary_permission->getConfigDependencyKey() => $temporary_permission->getConfigDependencyName()]
. Or useBundlePermissionHandlerTrait::generatePermissions()
. (SeeDrupal\Node\NodePermissions::nodeTypePermissions()
.)Didn’t we decide that
get('title')
is the same aslabel()
?Suggestion: call the variable
$id
instead of$perm
.Comment #156
quietone CreditAttribution: quietone at PreviousNext commentedHooray!
1. Thanks for the info about Url::fromRoute(). I have removed the link but I wonder if we should add a link to a wiki page that provides more information. Such as uninstalling the migrate module will remove the temporary permissions or why it is a good idea to remove them.
2. It was a good idea at the time? I was off with the fairies? It is all removed. Yes, good point about scope.
3. Change the permission name to be plural and removed the description property.
I created the permission because of errors like this:
which makes sense because the entity is defined with
admin_permission = "administer temporary permission"
. (If this is kept maybe that should be plural.)It can be changed to
admin_permission = "administer permissions"
and with corresponding changes in the json test, that works. However, it also means that migrate has a dependency on the user module for that permission. Is that really what we want to do?4. Fixed.
5. Reworked to model off of NodePermission. Much nicer now.
6. Yes, we did and I missed that one. Fixed now.
7. Yes, that makes it easier to read.'
Let's see what the test bot thinks.
Comment #158
quietone CreditAttribution: quietone at PreviousNext commentedI forgot that the new config entity needs to be enabled in an update hook.
Comment #159
benjifisherI am getting ready to leave for DrupalCon (early tomorrow morning) so I do not have time to look at the code, but I can reply to some points in #156.
The
user
module is required, so I am not worried about adding a dependency on it.As far as I can tell, the new permission is never used. Maybe, if we add CRUD routes to the TemporaryPermission annotations, the Entity system will use the permission declared in the annotations.
It looks as though
migrate_drupal_ui
has a custom access check foruid = 1
instead of relying on permissions.It seems simpler to me if we do not introduce a new permission, but you can leave it in if you disagree. Either way, we can reconsider when we work on the follow-up issue #3272352: Add a UI for deleting temporary permissions.
The test errors are a result of changes made in #2571235: [regression] Roles should depend on objects that are building the granted permissions. In the "Proposed resolution" section of that issue, I added this:
Comment #160
benjifisher@quietone:
I reviewed the entire patch in #158. It looks really good, but I think there are some more things we can clean up.
Can we use a more descriptive variable name, like
$permission_names
, instead of$all
?Same request here:
Change “entity” to “entity type” in the comment. Now that we have removed the other part of this function, maybe we can consolidate the doc block and the in-line comment.
We made the label plural, but not the machine name. Can we be consistent? (Maybe I have an agenda: it is just as much work to replace “administer temporary permission” with the plural as it is to replace it with “administer permissions”. Either way, you have to update here, the entity annotations, and the tests.)
Comparing with NodeType, I think this should be “Temporary permissions” (sentence case and plural). This will be used as the title or heading when (if?) we add a page listing the temporary permissions. At least, I assume that is how this is used.
It is good and simple to have an entity class that is all annotation, as you pointed out in #146. But I think a static method, taking
$permission
and$migration
(strings) and returning an array keyed by'id'
,'title'
, and'migration_id'
, would make this patch more DRY. I see a lot of places where$storage->create()
is called, and each one has the samepreg_replace()
.If you can think of a better place to put the helper function, then we can keep this class as “all annotation”.
If we use
TemporaryPermission::loadMultiple()
, then the$storage
property is never used. If we want to make the class easier to test, then we should keep the dependency injection and not hard-code the class name. Otherwise, we can remove this property, the constructor, thecreate()
method, the interface, and the relateduse
statements.When you switched to using
BundlePermissionHandlerTrait
, you forgot to update this doc block. The parameter is a single entity, and the return type isstring[][]
.(same snippet) I do not think we want to translate the permission names, since we are just using the machine names from the migration. That means we do not need
StringTranslationTrait
.The first code comment does not really apply to the line that follows, and it duplicates the next comment. Since
$storage
is used several times, I would like to make it easier to find: move it to the very top of the function. I do not think it needs a comment.We can simplify this a bit:
$storage->load('foo_permission')->delete();
.It looks as though the code was refactored and we forgot to update the variable name:
(same snippet) Can we test
!empty($temporary_permissions)
before even defining$list
? At the same time, consider changing$list
to$permission_names
(cf. (1)) and usingarray_map()
.(same snippet) Currently, the destination plugin saves the temporary permissions as a migration message, not a log message. Either change “Check the logs” here, or save it as a log message in the destination plugin. (I am thinking both as a log message and a migration message, but maybe just a log message.)
Would it make sense to move this method from
MigrateUpgradeExecuteTestBase
toMigrateUpgradeTestBase
so that we can use@inheritdoc
here?Comment #161
quietone CreditAttribution: quietone at PreviousNext commentedThis patch intends to address all the points in #160. It was all pretty straightforward, just two items to comment on.
6. I took a slightly different approach here by using preCreate(). As I was making the changes for #160 it seemed easier to just remove the id from the various create methods and do it in TemporaryPermission. This entity is not one contrib or custom code should be working with so moving the ID there seems fine to me. Whether that is kept of not maybe we should mark TemporaryPermission as internal.
15. I do not want to move those methods. The file MigrateUpgradeExecuteTestBase is intended for use only by tests that execute the upgrade where as MigrateUpgradeTestBase handles ones that check the various Migrate UI forms and tasks done before a migration is run. It does not help that there are abstract function declarations in MigrateUpgradeTestBase (getEntityCounts) that only make sense when running a migration. Their presence clutters up several Functional tests and I would really like to move those to MigrateUpgradeExecuteTestBase. I can also be convinced to move them.
Comment #162
benjifisher@quietone:
Thanks for the quick update!
I will try to remember the distinction between the two testing base classes. I figured it would be easier to ask you than to figure it out myself.
I think this issue still needs another round of cleanup:
I think we can just use
string
for the@return
comment. I checked the PHP docs forpreg_replace()
: you do not get an array when all arguments are strings, and the only time you getnull
is if there is an error.Same file: any new functions should declare a return type.
You suggested making this class
final
, but did not do that. For now, someone can extend the class and change thebuildId()
method. To support that, I think we should callstatic::buildId()
instead ofself::buildId()
.If we need this method, then I think it should be a member of the storage class, not the entity class. For one thing, that is where
load()
andloadByProperties()
are defined. For another thing, usingDrupal::entityTypeManager()->getStorage()
instead of dependency injection makes this method hard to test.This method is only called once, in
EntityUserRole
. It is easy to change that call to usebuildId()
instead. The simplest thing thing is to do that and remove this method.Follow-up to #160.9: remove the two
use
statements forStringTranslation
.In other places, you removed the
$permission
variable. We can do the same thing here.Follow-up to #160.11: just
delete()
.Follow-up to #160.13: If
$temporary_permissions
is not empty, then$permission_names
should not be empty, so the second test is redundant.We want a logger, not the messenger service.
Comment #163
quietone CreditAttribution: quietone at PreviousNext commentedAt least the trend is going down, it was 15 points of cleanup and now it is 9.
1. Ah that was phpstorm and I didn't check it.
2. I've added return types to all new functions per https://www.drupal.org/docs/develop/standards/coding-standards#s-paramet....
3. I choose not make a change earlier so I could ponder some more and hear your thoughts. I can't think of a use case where someone should be mucking around with the temporary permissions. Is there? This is still todo.
4. I thought that as well, until I started looking into it and the I found that loadByName exists on FieldCong and FieldStorageConfig. But, it is removed anyway.
5. Done.
6. Done.
7. Done.
8. Oops, Fixed.
9. Why? The migrateMessenger logs to the 'migrate' channel.
Comment #164
quietone CreditAttribution: quietone at PreviousNext commentedI missed that the check failed. This removes a return type declaration.
Comment #165
benjifisher@quietone:
Thanks for all the fixes. I reviewed 1, 2, 4, 5, 6, 7, and 8, and they all look good.
Re (3): Should the TemporaryPermissions class be
final
?By default, I do not make classes
final
. I may not anticipate how the class might be extended and used, but that does not mean that it will not be extended. Maybe some other module will want to create config-based permissions, and they may want to extend this class. Why require them to copy it instead? Maybe the module needs to use a different set of allowed characters, or create theid
based on something other than thetitle
.Do you have any other reason for declaring it
final
?Re (9):
I did not know that. Thanks for pointing it out!
The code from this patch is
and that constant is
2
. Looking atDrupal\migrate\MigrateMessage
, I seeSo it looks to me as though the current code will create a Notice, and that we could make it Info or Error. But we want a Warning, and we cannot get that with MigrateMessage.
Even though I did not understand MigrateMessage in my first comment, I still think we should use a logger.
I guess I will have to do some manual testing to see the log messages.
Comment #166
quietone CreditAttribution: quietone at PreviousNext commentedCan we just make this one line change instead of injecting the logger in to the destination plugin?
Comment #167
benjifisherI see a couple of problems with this:
For consistency with the other keys, I would use
'warning'
instead ofMigrationInterface::MESSAGE_WARNING
.The more serious problem is that this is not BC. Previously, any call to
MigrateMessage::display()
that uses the new key would log a Notice. Now it will log a Warning.It looks to me as though you are using the MigrateMessage class for something it was not designed to do.
Comment #168
quietone CreditAttribution: quietone at PreviousNext commentedOne injected logger coming right up!
Comment #169
benjifisher@quietone:
I am going to do some manual testing now, starting with a D7 database that has some "phantom permissions".
Are you still considering whether to make the TemporaryPermissions class
final
?Comment #170
benjifisherManual testing - Drupal 7 upgrade in the admin UI
I tried the following test, both with and without the patch in #168. When testing without the patch, I deleted the following lines from
d7_user_role.yml
:migrate_drupal_ui
along with its requirements./upgrade
.Without the patch, I got error messages in the Drupal log from the
migrate_drupal_ui
channel:Curiously, I got two copies of each message for each affected role (Authenticated and Administrator). One copy is prefixed with "Source ID 2:" or "Source ID 3:". Maybe, somewhere in the code, we log an error message and then (re)throw an exception.
In the Drupal 9 Standard profile, the Administrator role automatically gets all permissions, so it is not assigned any individual permissions. Ideally, we would detect this in the migration and not add individual permissions to the admin role. (That is not in scope for this issue. I have not checked: we may already have an issue for that.)
With the patch, I get two warnings in the
migrate
channel. One isI also get two errors (with the "Source ID #:" prefix). I also get a status message (warning) in the admin UI:
The warning messages (logs and UI) are expected. :+1:
Why do we still get the error messages? My first guess is that we have to clear the entity cache. I am not sure whether we should do this after defining a temporary permission or when getting them (with
TemporaryPermission::loadMultiple()
.) If we do it when creating the temporary permission, is it worth doing it once at the end of the loop (if any temporary permissions have been created)?One other minor point: the destination plugin is defined in the
user
module, so maybe we should use that logger channel.Summary:
migrate_drupal_ui
.Comment #171
benjifisherComment #172
benjifisherP.S. I forgot to check the migrate messages. It works as expected:
Comment #173
quietone CreditAttribution: quietone at PreviousNext commentedHmm, this is due MigrateMessage which assumes that messages are only 'status' and 'error'. I think that it too restrictive and should be changed. But then again this is the first time we have run into needing a 'warning' so maybe it's not an issue.
Anyway, I changed the destination to only use the logger, now with the 'user' channel. This now means there will be no message in the migrate_message table. The message is changed because I think it is helpful to add something from the source row, and I chose the role name for that. The message is now like.
The role 'anonymous user' has the following permission(s) that do not exit, 'migrate test anonymous permission.
I wasn't really happy with the 'migrate' channel either, I used it because it is a destination plugin. But that wasn't right either from a Migrate UI point of view, where all the messages are logged to 'migrate_drupal_ui'. When /upgrade is complete the final message includes a link (to route migrate_drupal_ui.log) to filtered logs. But now the permission errors are not there anymore. How is the user to find them?
As for using @final, I am not able to make a good argument so will follow your lead for now.
Comment #174
quietone CreditAttribution: quietone at PreviousNext commentedI think this will do what we want. The display message is a warning, the migrate_message is a \Drupal\migrate\Plugin\MigrationInterface::MESSAGE_WARNING and the watchdog message is a \Drupal\Core\Logger\RfcLogLevel::WARNING.
Comment #175
benjifisher@quietone:
I re-tested with the patch in #174, and it works as expected. Nice job! I am glad that the fix is simpler than my first guess (in #170) of clearing the entity cache.
The only part I do not like is this hunk:
That seems like too much of a special case: the batch processor should not "know" any details about the messages it is given.
I suggest that we revert that one change, accept error messages for now, and have a follow-up issue to update the batch processor (and the MigrateMessageCapture class) so that it pays attention to the
$type
of the message. I am adding the "Needs followup" tag for that.One other thing: since we agreed not to declare the class
final
, make the change suggested in #162.3.Summary:
static::buildId()
instead ofself::buildId()
.Comment #176
quietone CreditAttribution: quietone at PreviousNext commentedYea, I wasn't happy with it either but it did what we wanted.
#175
1. Revert done.
2. change made
Follow up made, #3278115: Make the migrate messages aware of the error level so removing tag. I think it could use a better title.
Comment #178
quietone CreditAttribution: quietone at PreviousNext commentedThe failure is a know random, Drupal\Tests\layout_builder\FunctionalJavascript\BlockFormMessagesTest::testValidationMessage, #3055982: Remove resizing window in BlockFormMessagesTest::testValidationMessage.
Setting to NR.
Comment #179
benjifisherThe failing test is mentioned in #3268070: Temporarily skip even more failing tests. I agree that we can ignore the failure here.
I compared the patch in #176 to the one in #168, which I tested. The differences are small, so I did not re-test.
Thanks for all your work on this issue, @quietone! I think the patch in #175 is ready to go.
Comment #180
quietone CreditAttribution: quietone at PreviousNext commentedHappy May Day to us!
Comment #181
benjifisherVirtual high five!
Comment #183
benjifisherIt looks like another random test failure, this time
CKEditor5IntegrationTest::testArticleNode()
. I ran the test locally, and it passed, so I am moving this issue back to RTBC.Comment #184
alexpottA couple of initial thoughts. I've not done a full review yet but somethings spring to mind.
I wonder if rather than a configuration entity we'd be better to use some simple configuration or state. Maintaining a configuration entity and all the Rest and Json stuff feels quite OTT. Or maybe you can use the
* internal = TRUE,
in the annotation and avoid all of this.Very cool to see this go.
Comment #185
quietone CreditAttribution: quietone at PreviousNext commented#184.1 An earlier version of this patch did use a config object and not a config entity. That was changed to a config entity in order to take advantage of the config dependency system and also not have to use ConfigUpdated. Benjifisher explains this in more detail in #134.
Comment #186
alexpottOkay then the configuration entity should be marked as internal. There's no way this should ever be exposed via JsonAPI or Rest.
I do feel like one config entity per temporary permission feels quite excessive and the whole idea of deploying and managing temporary permissions using config feels a bit befuddling. Part of me feels like we should be using the state system here. Like you do the migrations - get informed the system is maintaining these temporary permissions and you either have to enable the modules that provide them or lose them when you do a deployment. I'm going to ask for more opinion from other committers on this.
Comment #187
larowlanDiscussed this with @catch and @alexpott - and we were of the opinion that it should be ok to just drop these permissions.
People can always easily re-enable them if they chose to reinstall the module at a later date.
Failing that, can we use state to store these temporary permissions instead of a config entity?
Comment #188
quietone CreditAttribution: quietone at PreviousNext commentedSure state can be used, config was initially used because of #71.
This patch uses state to store the temporary permissions. After an upgrade from /upgrade, the user is given a message listing all the temporary permissions, then they are removed from the roles and deleted.
The IS and change record need to be updated, adding tags.
Comment #189
quietone CreditAttribution: quietone at PreviousNext commentedHere is a version that simply drops the non existing permissions, as was done back around #15.
Comment #191
quietone CreditAttribution: quietone at PreviousNext commentedOh, the d6 source plugin is sorted and the d7 one is not. Adjusting the D7 test accordingly.
Comment #192
benjifisherI am changing the title back to what it was before #139 and assigning this issue to myself for review. If it is still assigned to me after July 4, then feel free to reassign it.
Since we are going back to an earlier approach, I think an interdiff with one of the earlier patches would be more helpful than the one attached to #189. If I can figure out which of the earlier patches is closest, then I will attach an interdiff.
It looks as though the patch in #55 was RTBC before we postponed this issue on #2571235: [regression] Roles should depend on objects that are building the granted permissions. Can we use that as a starting point instead of #15? One change we made going from #15 to #55 was adding optional dependencies: see the last part of Comment #41, for example. I think that was a good idea. It means that we do not drop permissions that depend on configuration that is created as part of the migration.
Comment #193
benjifisherHere are a few quick questions about the tests.
Why remove the test for the destination IDs?
Why remove the type declarations?
Comment #194
quietone CreditAttribution: quietone at PreviousNext commented@benjifisher, thanks for the review.
193
1. It was an oversight and I have restored the assertion. (I don't think the assertion has much value and it potentially confusing as the D7 assertRole method does not have the same assertion. It is a long standing dream to have the same entity assertion methods for all d6/d7 migration tests. But that ship has sailed).
2. Another oversight.
192. The patch in #55 adds optional dependencies to d6_user_role and d7_user_role as well as the 'validate_permissions' configuration key to the destination plugin. I am running out of steam so the patch here adds the optional dependencies but does not modify the tests or re-introduce the validate_permissions key.
Comment #196
benjifisherIt looks as though the testbot set this issue to NW. Someone (maybe me) queued a re-test. The second test passed, so I am setting the status back to NR.
I am assigning this issue to myself for the review.
Comment #197
benjifisherI compared the patches in #55, #194.
$does_not_exist
from #194 and$new_permissions
from #55. Maybe$invalid_permissions
and$valid_permissions
would be even better.'validate_permissions'
. If not set, then we would migrate invalid permissions. Now that we are targeting 10.0, I do not think we should make that optional. See #135: we get an error when saving the role if we have added invalid permissions. In other words, it is a feature, not a bug, that the patch in #194 leaves out that option.I reviewed the patch in #194. It looks really good! I just have a few questions and suggestions for polishing it.
Do we have to provide a BC layer when we remove this option, or are we just following through on the deprecation we already made? I hope we can just remove it!
It is inconsistent to apply
array_keys()
in the constructor for one property and in thecreate()
method for another property. I think I like doing it in thecreate()
method: if we want to test this class with an explicit list of permissions, then we can just pass in the array of permissions instead of mocking the permissions handler.(same snippet) Thanks for breaking the long line in the
create()
method. I see that it is a single line in the parent class. I think we should either keep it on one line, for consistency with the parent, or put the closing),
on a new line.Unless you feel strongly, can we use multi-line syntax for all of these arrays?
It would be simpler if we could use something like
foreach ($messages as $count => $message)
, but the return type ofgetMessages()
is\Traversable
, so I guess we cannot rely on they keys like that.The parameter
$original_rid
is now unused. We do not have to deprecate it for BC, do we? Oh, good: this method is explicitly marked@internal
.I think we will have to remove it from the doc block, the function declaration, and all calls to the function.
The test is much better with the explicit lists of permissions.
We should think about additional test coverage. I guess the kernel test implicitly tests at least two things:
Ideally, we would have some more fine-grained tests of these. Maybe a unit test for the destination plugin. Is it worth adding a kernel test to compare the result of the role migration with and without an optional dependency?
Comment #198
benjifisherI reviewed some of the older comments.
hook_migration_plugins_alter()
, can't we? Or does that run too late, after dependencies have been processed? I would add the tag for a CR update, but it is already there.The more recent comments (starting with #187) suggest that it is OK to lose some migrations, if it saves a lot of complexity. I think it is worth adding optional dependencies for the core migrations. That is not a lot of complexity, and we reduce the number of permissions that have to be added back.
In fact, we might even have a follow-up issue to add optional dependencies for some common contrib modules. The precedent is all the custom code we have to support text filters from contrib modules.
Comment #199
quietone CreditAttribution: quietone at PreviousNext commentedMusic to my ears! :-)
197. I've updated the variable names in the destination plugin
197
1. I'll think about this later.
2. Done
3. Now the same as the parent.
4 and 5. These points are likely no longer relevant because the test now uses a data provider to allow for a test with the minimum of modules and a test with all optional dependencies. Adding the optional dependencies is the way to go so that when running from /upgrade the dependencies will run first.
6. Todo. I'll probably convert this to using a data provider as well.
I don't recall writing a unit test for a destination plugin. On searching I see that the only destination plugins that have unit tests are in the migrate module. Based on that I am thinking that the kernel tests will be sufficient.
hook_migration_plugins_alter() can be used to alter the dependencies of a migration. It is done in Commerce Migrate, commerce_migrate_commerce.module.
Comment #200
benjifisherI do not have time now to review the latest changes.
Maybe it is worth adding a test based on the results I reported in #135 (using the test migration from #133). A test-only patch would support the Critical status of this issue.
Thanks for confirming that hook_migration_plugins_alter() can be used as I suggested in #198. I am adding a note to the "Remaining tasks" in the issue summary.
Comment #201
benjifisherThe patch in #199 addresses most of the points in #197 and #198, Maybe #184.2 already answers #197.1. I added a "remaining task" to the IS for #198.2. I think that about covers it.
The new patch made a lot of changes to the tests, and I have several minor changes and suggestions for that:
From the way it is used,
$expected_permissions
isstring[][]
. The outer keys are destination IDs (?) and the inner keys are'valid'
(?) and'invalid'
.Can you change the key
'roles'
in the data provider to match the parameter name$permissions
? Or maybe change both to something likerole_data
, since bothroles
andpermissions
suggests a flat list of strings.(same snippet) Suggestion: shorten the comment to "… that may provide permissions."
(same snippet): We should use the
$modules
parameter. Optionally, we can eliminate the variable$module_installer
:Alternatively, make the parameter a boolean. It seems odd to list the modules in the data provider and then install related configuration in this function.
(same snippet) Document the parameters in the doc block. I am not sure that this is standard practice for test methods, but it seems like a good idea to me.
I think this duplicates an assertion earlier in the test. Do we want to add
migrate_test_role_41
to the helper function?Comment #202
quietone CreditAttribution: quietone at PreviousNext commentedThis patch should address all the items in #201. And good catch on point 6.
Shall I make the same changes to the Drupal 7 test? That is, test with the current dependencies and then will all of them?
Comment #204
benjifisherThat is not a random test failure, so the issue really NW.
The default answer to "should I add test coverage?" is "of course", but maybe this is an exception. What we are testing here is how the upgrade handles dependencies, which should be independent of the sources (D6 or D7). Furthermore, I think we have agreed to keep the D6 source plugins until we are ready to move the D7 sources to contrib, so we will not be in a situation where we are removing test coverage.
In short, I think we can leave the D7 tests as is.
Comment #205
quietone CreditAttribution: quietone at PreviousNext commentedThe failures are due to #3082211: Migrate UI upgrade tests should provide the complete log, which has been reverted. I am restarting the tests.
Comment #206
quietone CreditAttribution: quietone at PreviousNext commentedI figured out the failure in sqlite by editing the results to place each permission on a new line.
Expected
Actual
This is the list of invalid permissions that is included in the migrate message and then later tested. Let's fix this by sorting the permissions before they are added to the message. And in the test make sure the expected_permissions array is sorted.
Comment #207
quietone CreditAttribution: quietone at PreviousNext commentedOops, redoing the patch because I forgot to update the d6 test.
Comment #208
benjifisherIn #201.6, I was thinking that
assertNoDuplicateRoles()
duplicates the assertionassertEmpty()
. I missed that there wasexecuteMigration()
in between. Is there any reason not to useassertNoDuplicateRoles()
twice?Other than that, the patch in #207 addresses all the points in #201.
Comment #209
quietone CreditAttribution: quietone at PreviousNext commentedNot really, we are testing more possible duplicates than we need to in each case but I do like that it makes the test a wee bit easier to read. The only reason I initially didn't use it twice was to not introduce another change to the test.
Comment #210
benjifisher@quietone:
Since this issue introduces the helper function, I think it is in scope to use it to simplify the test. But I will defer to you: if you want to avoid further changes, then that is OK.
I am going to work on the CR now. Before RTBC, we should handle the "Remaining tasks" in the issue summary. I think some of those are obsolete, since we switched from the "temporary permissions" approach back to the "remove invalid permissions" approach.
Comment #211
quietone CreditAttribution: quietone at PreviousNext commentedJust a few changes to the Issue Summary.
Comment #212
benjifisherI did some manual testing and updated the issue summary and the
change record. I noticed a couple of problems with the feedback.
When testing manually (see #133, #135) I get the following
migration messages:
I would like to see each permission enclosed in single quotes. Just
add single quotes to the
glue
parameter toimport()
:(same snippet) Despite the code comment, I do not see the
messages in the Drupal log. Since there is not yet a way to see
migration messages in the UI (using just Drupal core) I would like to do
what the comment says.
I am removing the tags for IS and CR updates. I am leaving one item in the "Remaining tasks" section: review the CR.
Comment #213
quietone CreditAttribution: quietone at PreviousNext commented212.1 Fixed.
212,2 In my opinion the comment is the mistake and I have changed it. I think we should keep the migration system as is where migrate messages are only logged when using the UI.
Comment #214
benjifisherI reviewed and re-tested, and the patch in #213 adds the single quotes as intended. Thanks for fixing that!
I updated the change record, removing the instructions for reviewing the message at
/admin/reports/dblog
.The only remaining task in the issue summary is to review the change record. I will not let that get in the way of marking this issue RTBC.
Comment #215
alexpottIs there any reason we're not targeting 9.5.x too here? I can't think of why really. Also don't we need to take care of existing roles that have been migrated using the skip_missing_permission_deprecation flag?
Comment #216
quietone CreditAttribution: quietone at PreviousNext commentedAdded an update function (pretty much the same as the one added in #2571235: [regression] Roles should depend on objects that are building the granted permissions, which is in 9.5.x). And made a 9.5 patch.
Comment #217
alexpott@quietone nice update function :)
The thing I don't understand is why this update wouldn't be part of 9.5.x too? I read the debate in the slack channel but I'm not sure why we'd wail till 10.0.x to run the update function.
Comment #218
benjifisherComparing the two patches in #216, there are three things in the 10.0 patch that are not in the 9.5 patch:
user_post_update_update_migrated_roles()
'skip_missing_permission_deprecation'
inDrupal\user\Entity\Role
The patch for 9.5 does remove
'skip_missing_permission_deprecation'
from the two core role migrations.I think these four changes should go together, so back to NW.
I am still bothered that in #2571235: [regression] Roles should depend on objects that are building the granted permissions we decided to issue deprecation notices in D9 when saving a user role with invalid permissions, but throw an exception notice in D10. That seems like an implicit promise that migrations would continue to work the old way until D10. But that conflicts with the promise that 10.0.0 will be "as close as possible" to 9.5.0, so I guess we have to choose the lesser evil.
Comment #219
quietone CreditAttribution: quietone at PreviousNext commented@alexpott, thanks but I did not write the update function. It was added in #2571235: [regression] Roles should depend on objects that are building the granted permissions and I reused it in Drupal 10.
#218
1 and 2. The update function
user_post_update_update_migrated_roles
was not added to 9.5.x becauseuser_post_update_update_roles
exists in Drupal 9.5. The two functions are identical. The names are different because I got an error message (don't recall the details) in D10, so I changed the name. And, then there is no need for a new test because the test already exists.3. Changed as requested.
Comment #220
catchNot sure about making this change in 9.5.x. But if we don't throw the exception, then roles could start to accumulate invalid roles again, and we'd have to add another update in 10.x again to remove them again. Which suggests leaving it in 10.0.x to me.
Comment #222
benjifisherFrom #219:
I know this is an edge case, but suppose that a site has an ongoing migration that creates or updates user roles. And suppose that it takes advantage of the
skip_missing_permission_deprecation
option to add invalid permissions to those roles.When the site was upgraded to 9.3, the invalid permissions were removed by
user_post_update_update_roles()
. But the migration continued to run periodically, and invalid permissions were re-introduced while the site used 9.3 and 9.4. Drupal keeps track of update functions and will not run them more than once, so those invalid permissions will persist when the site is upgraded to 9.5. We are removing the special processing of the option, so continued migrations will not add new, invalid permissions after upgrading to 9.5.But the point of #220 is that the patch in #219 changes the behavior to match Drupal 10: throw an exception when saving one of the roles with invalid permissions. That can happen when submitting the Permissions form, leading to a WSOD.
As I said in #218, these changes should be made together. If we make the changes all at once, including a copy of the update function with a new name, then existing invalid permissions are removed when upgrading to 9.5, and then no new ones are created.
Maybe I was wrong about the test. If we have two identical functions with different names, then we do not have to test them both. I might make one a wrapper for the other, and have the test use the wrapper, but maybe I am being pedantic.
Comment #223
quietone CreditAttribution: quietone at PreviousNext commentedThe 'new' update function is the same name as the one added in the other issue but with '_followup' at the end. It is used in both the D9 and D1o patches. I think that helps to understand why there are identical post_update functions. I have renamed the update test so it can be the same in D9 and D10 and added a few assertions.
I was going to add my thoughts on adding this to D 9.5 but I will refrain and let others decide. The twists and turns of this issue has taken it's toll.
Comment #224
benjifisherI feel pretty strongly about my comment "these changes should be made together" in #218 and #223. My personal preference is to make them in 10.0 and not 9.5. But so far, I have not convinced @alexpott of that, so I guess we will make the changes in both branches.
I will review the patches in #223 after lunch.
For now, I am attaching an alternative interdiff for the 10.0.x patches in #216 and #223. The interdiff attached to #223 shows the renamed test as a deleted file and an added file. I generated mine by applying both patches to different branches, starting both from 10.0.x. Then I used
git diff foo..bar
.Comment #225
benjifisherFor the 10.0 version of the patch in #223, I have two minor complaints:
I think we are testing removed permissions but not dependencies, so this summary line is not accurate.
I think the last three lines should be inside the
if
block. This is the same comment I made in #105.1.Comment #226
benjifisherI reviewed the patch in #223 for 9.5.x. The important thing is that the related changes are all made together, so I am satisfied there. Again, I have some minor requests:
Same as #225.1.
Same as #225.2. It is less clear in this branch, since it means updating an existing function (or copy/paste/update that function). It seems to me that updating an update function is fairly non-disruptive, but I will not insist if you disagree.
Comparing the patches for 9.5 and 10.0, I notice that the 10.0 version uses the fixture for 9.4.0. Is there a reason to use the fixture for 9.3.0 here?
Comment #227
quietone CreditAttribution: quietone at PreviousNext commented@benjifisher, thanks for reviewing again.
225. Everything here should be done.
226. Everything here should be done.
Comment #228
quietone CreditAttribution: quietone at PreviousNext commentedComment #230
benjifisherIt looks as though there is a reason to use the 9.4.0 fixture in the 10.0 version. Both 9.3.0 and 9.4.0 fixtures are available in the 9.5.x branch.
Comment #231
quietone CreditAttribution: quietone at PreviousNext commentedRestoring the 10.0.x patch
to use the 9.4.0 database dump.
Comment #233
quietone CreditAttribution: quietone at PreviousNext commentedFailed on an unrelated test, Drupal\Tests\book\Functional\BookTest::testGetTableOfContents, retesting
Comment #234
alexpott@catch pointed out to me that we shouldn't be changing this to an exception in 9.5.x - it'd be disagreeing with out past selves! I'm really really sorry I missed this.
I think the approach should be to get #231 in 10.0.x asap and then consider backporting the migrate changes without the update and Role changes to 9.5.x - so migrations to 9.5.x can take advantage of the new code. We might not have time to add this to 9.5.0 though... we'll see.
Comment #235
benjifisherI have reviewed the changes between #223 and #231, and they are pretty minor. They include the two changes I requested in #225 and renaming the post-update function.
Renaming the post-update function is something we still want to do, in case we do get this issue fixed for 9.5 as well.
@alexpott, I hope you will review #225.2. Maybe you already have. If I am right, then it fixes a mistake that we made in #2571235: [regression] Roles should depend on objects that are building the granted permissions.
I am marking this issue RTBC for the 10.0.x branch, although I have not done any manual testing since #212. I am attaching an interdiff comparing the patch from #207, which I tested, to the patch in #231.
Comment #236
mikelutz+1 to #231 for 10.x
Comment #237
alexpott@benjifisher #225.2 is correct - nice spot!
Committed and pushed 247bd31efd to 10.1.x and 1002797d91 to 10.0.x. Thanks!
Setting back to 9.5.x to discuss if we want to backport only the migration and non update part of these changes. I'm not sure I could go either way and given the amount of time we have maybe it's best to just call this fixed in 10.0.0 and move on.
Comment #240
benjifisherI am happy to leave 9.5 the way it is. I think I said as much in #218.
In #217, @alexpott mentioned some discussions on Slack. Most or all of that discussion is on
in case anyone else wants to review it.
Comment #241
quietone CreditAttribution: quietone at PreviousNext commentedI too am happy to have this committed only to D10.
I was about to mark this Fixed but then I remembered the CR. It still needs to be reviewed.
Comment #242
benjifisherI published the CR. I also closed (cannot reproduce) a related issue that has been Postponed (MNMI) for a while.
I think we have consensus, so I am changing the target version back to 10.0.x and marking this issue fixed.