Follow up to #2849827: Move workflow "settings" setters and getters to WorkflowTypeInterface.
Problem/Motivation
Currently we have typical entity access for workflows, with the addition of "delete-state:foo". This is useful because for content_moderation, we don't want users to be able to delete states which are required for integrity.
After #2849827: Move workflow "settings" setters and getters to WorkflowTypeInterface, we delegated the responsibility of all state/transition storage to the workflow type plugins, essentially freeing types to be able to introduces states and transitions which weren't necessarily based on configuration within the workflow entity. Now states and transitions can be based on any 3rd party configuration or even predefined (days of the week? drupal user roles? commerce checkout panes? etc.).
Based on this, is there more scope to expand the workflow entity access system to encompass "edit-state:foo", "edit-transition:foo" and "delete-transition:foo". If I'm providing a workflow type with states based on something external to the workflows entity, restricting "edit" to states will likely make a lot of sense. To see this in action:
1. "drush en workflow_type_test -y"
2. Create a workflow of type "Predefined States Workflow Test Type"
3. See "edit" buttons which doesn't make sense, based on the states being totally pre-determined.
Proposed resolution
Expand the access controls.
Remaining tasks
Patch.
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #43 | 2896726-43.patch | 27.6 KB | sam152 |
| #43 | interdiff.txt | 1.18 KB | sam152 |
| #40 | 2896726-40.patch | 27.59 KB | sam152 |
| #40 | interdiff.txt | 6.21 KB | sam152 |
| #30 | 2896726-30.patch | 24.49 KB | sam152 |
Comments
Comment #2
sam152 commentedI think this would count as api additions, so I don't think it should be a blocker for stable.
Comment #3
sam152 commentedComment #4
sam152 commentedComment #5
sam152 commentedComment #6
sam152 commentedComment #8
sam152 commentedIt would be good to confirm that adding extra access operations is an API addition only. If that's the case, this can safely be done in 8.5.
Comment #9
wim leersSo you're saying that:
delete-state:foowill continue to existedit-state:foo,delete-transition:foo,edit-transition:fooIOW: we're confident that we will only ever add more access operations.
If my understanding is correct, then yes: this would be a pure API addition.
Comment #10
sam152 commentedPicking this back up, am I correct in saying based on #2300677: JSON:API POST/PATCH support for fully validatable config entities not being completed, introducing a more fleshed out permissions model for states and transitions would currently only need to apply access control at the UI level? Given we don't have any special handling for REST and the
delete-state:fooopperation, I think that sounds right.Comment #11
sam152 commentedHere is what I had in mind for this.
Comment #13
sam152 commentedComment #14
amateescu commentedThe documentation says '_workflow_access' but the code uses '_workflows_access' :)
FWIW, I like '_workflow_access' better, I don't see why we would need to match the name of the module instead of the workflow entity itself.
Comment #15
sam152 commentedI agree, _workflow_access does seem nicer. NW for that.
Comment #16
timmillwoodChanged all instances of
_workflows_accessto_workflow_access.In doing so noticed the docblock for
\Drupal\workflow_type_test\Plugin\WorkflowType\WorkflowCustomAccessType::workflowAccesswas pointing toworkflow_type_test_workflows_accesswhen the function in workflow_type_test.module isworkflow_type_test_workflow_access.Comment #17
sam152 commentedGood catch! This is looking good to me :)
Comment #18
amateescu commentedMaybe "cant" => "can_not" is better english here? :P
Also, do we need to test the positive checks as well? (e.g. "can update", "can update transition", etc.)
Can we add " for the Workflow entity type" here to make it more clear that we're only doing it for one entity type?
This can be just {@inheritdoc}.
Comment #19
sam152 commentedComment #20
sam152 commentedComment #21
sam152 commentedWhoops, missed 1.1. This is what happens when two bits of feedback are put in the one bullet point ;)
Comment #22
amateescu commentedAwesome, looks great now :)
Comment #23
jibranFound some nits other than that patch looks great.
Can we please store workflow in local var?
Can we have a change notice for this please?
Comment #24
sam152 commentedLocal variable does make this a bit more readable.
I think a change record could mostly be pulled from the docs in the patch, I don't have time to put one together today, but can look at it later in the week.
Comment #25
sam152 commentedComment #26
jibranThanks, for addressing the feedback.
The first condition of if statement is same do you think we should make it nested?
Comment #27
wim leersSupernit: I think it's "cannot", not "can not"? #nonnativespeaker
Supernit: (about both strictness and legibility)
What's interesting here, is that despite us making changes it to the stored "state", to change the list of forbidden operations, the 200s turn into 403s automatically, without clearing any caches.
How is that possible?
What about custom routes using this access checker? Why are we able to not provide BC?
Comment #28
sam152 commentedThanks for the review!
WorkflowDeleteAccessCheckclass itself is marked@internal.Comment #29
wim leers#28 👍, and #27.4: 👌
Comment #30
sam152 commentedAddressing the code feedback from #27.
For some reason I thought
EntityAccessControlHandlerhad its own caching, but it's just a static cache. I think then I'll have to look at the whole response object, see why it's not cacheable.Comment #31
sam152 commentedI started looking into #27.3. My findings were as follows:
The request to these pages were uncacheable for the following reasons:
I implemented the following in an attempt to make the page cacheable:
This created a cache entry in
cache_dynamic_page_cache:Then in
\Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::onRouteMatchI expected this to be delivered on the second hit. Stepping into render cache from here, into\Drupal\Core\Render\RenderCache::getthecidused to lookup the cache hit was the following:This obviously doesn't match the entry in the DB, so not sure why it was unable to derive the correct cache ID. I figured that it was possibly due to the fact this is an unconventional code path, ie, the safeguards I commented out probably exist for a reason and undoing them is undefined behaviour. Possibly a bug in render cache or dynamic_page_cache too?
At this point I decided to conclude my adventure, I hope you'll understand :P
Comment #32
sam152 commented@Wim Leers any chance of a sign off on the cacheability part? I think we're back to RTBC if that's all good.
Comment #33
sam152 commentedAdded the change record here: https://www.drupal.org/node/2929327
Comment #34
jibranPatch looks ready to me. Just some minor nits.
This can be injected.
I think we should add this to cache context as well.
We can avoid calling
assertSession()again and again in the loop if we'll create a local variable.Comment #35
sam152 commented1. I tried this and it made the test plugin more confusing because of the naming conflict between state storage and workflow states.
2. I think the entity access system will add all the contexts required to cache these properly. What the new test cases indicate is that nothing beyond the users permissions are currently being used to make a decision about access, so an access check for
update-state:foocould be cached for all entities. This isn't the case for some of the existing operations likedelete-state:foo. It checks things like the required states, number of states etc, so theWorkflowentity is added as a cacheable dependency. As far as caching the access checks per'route'context, this must be implied somewhere else, given it's not something\Drupal\Core\Entity\EntityAccessCheck::accessadds either. Not sure where though.3. This is a style thing and one that isn't widely accepted. 100 instances of
\$(.*)= \$this->assertSession(), 500 instances of$this->assertSession()->.Comment #36
sam152 commentedAnyone interested in reviewing this again? I believe all the points have been addressed.
Comment #38
sam152 commentedTentatively back to RTBC given mostly nits were fixed in #30 and I think I've addressed most of the feedback.
Comment #39
larowlanDid that ever happen?
Although this can be removed, because it is internal - can we use this as a BC shim around the new access check and mark it deprecated instead?
See https://www.drupal.org/core/deprecation#internal
Comment #40
sam152 commentedThanks for taking the time to review this again @larowlan. I've restored the original access check as a BC shim with deprecations.
Re: signing off, I wasn't able to get in touch with @Wim Leers for a thumbs up however I did a deep dive into why our test didn't require any cache clears in #31.
Comment #41
timmillwood@Sam152 - #41 looks good, and I would RTBC, but I guess we should get a review from @Wim Leers first.
Comment #42
wim leers#32, in December 2017, or >3 months ago…:
I'm so sorry :( I never saw these notifications!
#31: of course, admin routes aren't cached by Dynamic Page Cache. Sorry for not having thought of that before asking my question.
Also re-reviewed the patch:
❤️
"For add"?
Woah, TIL! That's capturing + labeling the matches, right?
Did we ever considering doing
? i.e. custom entity operations? That's totally a supported thing!
That last question is the only reason I'm not moving this to RTBC just yet. Point 2 is a nit.
Comment #43
sam152 commentedThanks for looking at this again @Wim Leers.
_workflow_accessends up translating into a custom entity operation, it's just a case of if_workflow_accessor_entity_accessmanages that for us. Personally even if both can do the job, I think it's nice to be able to use 'add-state' alongside 'update-state' and 'delete-state' which_entity_accesscan't do for us.Comment #44
sam152 commentedDoes anyone have a strong opinion that
_workflow_accessshould not support "add" operations alongside update and delete if the equivalent also works with_entity_access? Personally I think it reads better from a consistency perspective and it's not a large amount of additional code to maintain.Comment #45
amateescu commentedI agree, so the patch looks ready to me :)
Comment #47
tacituseu commentedUnrelated failure.
Comment #49
larowlanAdding review credits
Comment #50
larowlanFixed on commit
Committed 5b224a4 and pushed to 8.7.x. Thanks!
Published change record.