Problem/Motivation
At present we don't have a generic entity API for determining revision support for operations such as:
- view
- delete
- revert
- edit
Node module defines the following permissions:
- delete all revisions
- revert all revisions
- view all revisions
But these are silver-bullet permissions, they don't allow granular access (per node-type, per entity etc).
The test entities in core don't do anything with revision (their routing has access: TRUE)
These entities don't have any UI for viewing/reverting/deleting revisions:
- media
- block content
- taxonomy
- menu link content
This is now starting to be a hard blocker for further generic improvements in core including:
- #2350939: Implement a generic revision UI
- #2795279: [PP-2] [META] Revisions support
- #2925532: Provide bulk action to delete all old revisions of an entity
Proposed resolution
Add new operations to the existing ::access signature such as
'view all revisions'to view individual revision or revision listing page.'view revision'to view individual revision'revert revision'to revert revision'delete revision'to delete revision
Remaining tasks
- Review
- RTBC
- Rejoice
User interface changes
None
API changes
_access_node_revision
| Route | Path | _access_node_revision op |
Entity operation |
|---|---|---|---|
| entity.node.version_history | /node/{node}/revisions | view | view all revisions |
| entity.node.revision | /node/{node}/revisions/{node_revision}/view | view | view revision |
| node.revision_revert_confirm | /node/{node}/revisions/{node_revision}/revert | update | revert revision |
| node.revision_revert_translation_confirm | /node/{node}/revisions/{node_revision}/revert/{langcode} | update | revert revision |
| node.revision_delete_confirm | /node/{node}/revisions/{node_revision}/delete | delete | delete revision |
_access_media_revision
| Route | Path | _access_media_revision op |
Entity operation |
|---|---|---|---|
| entity.media.revision | /media/{media}/revisions/{media_revision}/view | view | view all revisions |
https://www.drupal.org/project/media_revisions_ui moule will need a patch once this issue is committed.
Data model changes
N/A
Release notes snippet
The \Drupal\node\Access\NodeRevisionAccessCheck (access_check.node.revision) and \Drupal\media\Access\MediaRevisionAccessCheck(access_check.media.revision) services and there methods are deperecated. Developers using these services in their routing.yml can just use the _entity_access checker instead.
Similarly, to check access to a revision, you can now use the ::access method on the entity.
To check access to view a list of revisions use:
$entity->access('view all revisions');
To check access to view a single revision use:
$entity->access('view revision');
To check access to revert a revision use:
$entity->access('revert revision');
To check access to delete revision use:
$entity->access('delete revision');
| Comment | File | Size | Author |
|---|---|---|---|
| #172 | 3043321-170-9.2.x.patch | 49.43 KB | jibran |
| #154 | 3043321-153-interdiff.txt | 850 bytes | kim.pepper |
| #154 | 3043321-153.patch | 48.29 KB | kim.pepper |
Comments
Comment #2
larowlanOption 2 is less of an API break and the default implementation just returns Neutral so no harm no foul for those entity types that don't care
So leaning towards that
Comment #3
jibran#2809177: Introduce entity permission providers might be related here.
Comment #4
wim leersThanks to @larowlan for linking this at #2795279-66: [PP-2] [META] Revisions support.
Comment #5
hchonovWe would also have to consider #3023194: [PP-2] Add parallel revisioning support.
While I understand the problem, I think that introducing more operations might make the access API more complex.
\Drupal\Core\Entity\EntityAccessControlHandlerInterface::access()has the entity object as a parameter and therefore one could check its revision metadata.What if instead of adding new operations we add new permissions? A very rough example:
Comment #6
larowlanCan you elaborate on what you see the issues being here?
That would get very difficult on the permissions page, with 3 permissions (view/delete/update) for each entity-type + bundle combination.
I agree that we already get the entity in the access methods and have the ability to check if we're dealing with the default revision.
I wonder if we could attempt to move Node away from using its own access checks on the revision routes and bundle that into the node access control handler and have the routes just use _entity_access. If we could make that work, then the 'generic revision access API' is just the existing access API we have now, and the handlers need to deal with revisions - thoughts?
Also, I note that nodes permissions are poorly named - view all revisions, should probably mention 'node' in the name, same for delete.
Comment #7
jibranSomething like #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter()?
There was a time when nodes were the only revision-able entity. :P
Comment #8
aaronmchaleI'm leaning towards preferring Option 2, I also think that for the most part simple permissions are fine to cover the majority of use cases.
Also wondering if we should postpone this issue until we know what is happening with #2809177: Introduce entity permission providers as it will likely have an impact on the implementation here?
Comment #9
aaronmchaleThere is now a more agreed approach for #2925532: Provide bulk action to delete all old revisions of an entity , see comment #43 and #51 there. It would be worth identifying if there will need to be any changes to this issue to effectively accommodate the new revision action and the background clean-up, and if so determine if this issue will need to be postponed on that issue.
Comment #10
dpiFormatting only
Comment #11
dpiRe option 2, I think a "
view all revisions" operation would make sense, to control permissions for the Revisions listing page itself. It would control access to view all revisions for a singular entity. This operation would also as a master view access, before checking individual revision view access rights via "view revision" operation.Comment #12
dpiComment #13
alisonI'm not sure if this comment fits here -- if not, lmk, I can repost elsewhere :)
I want to be able to restrict access to viewing revisions of a node to users who have permission to edit that node -- rather than the current setup, "To view a revision, you also need permission to view the content item." We really don't like that users see "View" and "Revisions" tabs when they can't even edit that node :)
Also, it's awkward that we can grant permission to revert revisions on a node that a user can edit, but they can't get to the "Revisions" tab unless we allow them to get to the "Revisions" tab for all nodes on the site.
Anyway, just sharing! Thank you!
Comment #14
dpiSorry, bit late in posting this patch.
So as I understand it, going in operation direction is more about setting a standard and precedent, rather than creating a rigid API. Setting the standard with core involves converting nodes, and tests for generic entity types if any, plus making the node revision UI and revision routes use this operation while retaining existing behaviour.
Using 'revert' op instead of 'update revision' from summary since it maps to current Node permission. I suppose the operation effectively means to change default/change latest revision/point-to-this-revision-instead, as opposed to edit/updating a revision.
Node
Per-revision operations such as delete revision or view revision fall back to OP all revisions/OP BundleName revisions permissions. In cases where per revision access is desired without granting all revisions permissions, it may be implemented later, or in contrib.
NodeRevisionAccessCheck
NodeRevisionAccessCheck was gutted to use new operations, maintaining backwards compatibility.
The ultimate desire is to move node routes from using
_access_node_revisionto_entity_access. However_entity_access(\Drupal\Core\Entity\EntityAccessCheck::access) requires the entity to be upcasted, which they are not currently. Nodes routes are due to be upcasted in #2730631: Upcast node and node_revision parameters of node revision routes. We can add a follow up to this issue to switch over to to_entity_access, a@todoand deprecation listener exception was added in the meantime.The following routes currently use
_access_node_revision:_access_node_revisionopFor now, the access checker
entity.node.revisionroute makes use ofview all revisionsoperation. Even after this route is switched to use_entity_access: node.view revision, it will use the same logic to determine access.Code notes for NodeRevisionAccessCheck
NodeRevisionAccessCheckhad static caching,\Drupal\Core\Entity\EntityAccessControlHandler::accessalready has internal static caching with the same contexts, and more (ID, Revision ID, Langcode, Account ID, Operation).$this->nodeAccessvariable created in constructor of NodeRevisionAccessCheck. I believe this is permitted.$bundle_entityhas been cleaned up; made more efficient.Other notes
view all revisions, you must also have access to 'view' operation on the entity. See also permission description forview all revisionsand friends.Overall I think the patch is an improvement despite the overall logic remaining complex.
Comment #15
dpiKudos and credit to @larowlan for review and providing feedback.
Comment #16
wim leersWOW, @dpi, thanks so much for this enormous push forward!
Can you add some inline documentation for this? Or at least an
@seeto a place where I can find out how to interpret this mapping.Does "supplemental" imply "optional"?
The idea is to generalize this to all revisionable entity types' access control handlers, right?
Comment #17
dpi@Wim Leers
Added documentation there
No it doesnt, I've reworded it, thanks :)
Once a standard is set here, #2350939: Implement a generic revision UI would create UI for media/taxonomy/menu link content, along with appropriate permissions. Those permissions would map to the operations standardized here.
It would be the responsibility of each entity to map permissions to revision operations. Currently
EntityAccessControlHandleronly looks for admin permission. I don't think\Drupal\Core\Entity\EntityAccessControlHandlershould be modified to look for revision permissions.Media
I've just checked in on media and there is a revision route and checker very similar to the node implementation. Since entity.media.revision is upcasting entities, MediaRevisionAccessCheck can be fully deprecated without exceptions in
DeprecationListener. Media already has revision tests.Updated patch
Comment #19
aaronmchaleIf we're going to introduce a standard approach here then personally I'm not a fan of using white-space in the operation keys, e.g. "revert all revisions". I know we do it for permission keys but I'm really keen on finding a way (if we can) to move away from that. Would introducing these new access operations with underscores instead of spaces be too much of a BC break?
Comment #20
dpiTheres no technical reason not to for operations, operations are mostly just passed around during requests, arn't saved to any kind of storage. Any non-empty string of any length should be valid for operations, including odd symbols, whitespace, unicode.
We already have a couple of operations in core containing whitespace, including 'access taxonomy overview' (via #1848686: Add a dedicated permission to access the term overview page (without 'administer taxonomy' permission)) and 'view label' (via #2692091: Use the new 'view label' entity access check in the entity reference label formatter).
Comment #21
aaronmchaleOh yeah I know technically it doesn't matter, I just feel that as a developer white-space in keys always feels a bit wrong/odd/weird. Also from a consistency point of view there are more places in Drupal which don't allow white-space (mostly for technical reasons I imagine), and even places where underscores are converted to hyphens and vise-versa; I just feel it's good practice to be consistent. Of course you could equally argue that to be consistent would be to stick with spaces since they are common place in the existing commonly used permission keys. So I guess it comes down to which consistency is the better one to adhere to.
After writing and reading my own comment above now I'm just on the fence about it.
Comment #22
aaronmchaleThink the summary could use an update, since we seem to have settled on option #2 and it would be good to list all of the different operations which will be introduced.
Would also be great if we could get this in to 8.8.
Comment #23
wim leers@dpi Any chance you could fix the failing tests? :) This is looking really good! It would be lovely to still get this in Drupal 8.8 :)
Comment #24
jibranAdded two more deprecated messages.
Comment #27
aaronmchaleUpdated summary to reflect consensus reached on the second option.
Comment #28
aaronmchaleRerolling against 9.0.x
Comment #29
ravi.shankar commentedComment #31
aaronmchaleJust noticed I forgot to remove the rreroll tag.
Comment #32
johnwebdev commentedSo the reason NodeRevisionsUiBypassAccessTest is failing is due to access result caching. Let's see what happens now.
Edit:
Uhh... that shouldn't be there, my bad :)
Comment #33
johnwebdev commentedComment #35
aaronmchaleAdded:
Comment #36
psf_ commentedI think that issue #3035113 is waiting for this one too. (https://www.drupal.org/project/drupal/issues/3035113)
Comment #37
wim leers#36: correct. And since JSON:API is now in core, we can tag this :)
Comment #38
manuel garcia commentedReroll, three-way merge did the trick.
Comment #39
manuel garcia commentedActually ignore #38, incorrect reroll. This one should be good.
Comment #40
manuel garcia commentedJust getting the patch up to date, bumping the deprecation version numbers.
Comment #43
jibranFixed trivial test fails
Comment #45
jibranFixed my c/p errors.
Comment #47
jibranWe are missing coverage for bundle specific permissions here. We also need a change notice for the deprecated warnings added here.
Comment #48
aaronmchaleI noticed in the patch there is at least one deprecation trigger_error message that links to this issue, when a change record is created then that link should be updated to point to the change record.
Also, is the title technically correct? Reading the patch it doesn't look like we're providing a new API as such, we're just standardising some access operations and updating existing parts of core to use those new operations.
Thanks,
-Aaron
Comment #49
jonathanshawPer #47
Comment #50
jibranThis should pass as per mapping shared in #14.
Comment #52
jibranThis is actually blocked on #2730631: Upcast node and node_revision parameters of node revision routes so combined current patch with #2730631-128: Upcast node and node_revision parameters of node revision routes. Hopefully this will be green.
Comment #53
berdirDisclaimer: Coming in pretty blind her, didn't look at the code before, just had a quick look at the issue summary.
spaces look weird, but we already have that with "view label", so fine.
I know we have a new-ish rule that says you can use camel case if it's consistent in the file, but I think it's still rarely used and mixed, already here, so $media_storage.
Gut reaction to injecting one entity handler into another is a bit strange. Also, not a fan of injecting handlers to construct in general, I prefer injecting the entity type manager and getting the storage when we need it. Can get weird if a cache clear tries to re-create handlers (see \Drupal\Core\Entity\EntityTypeManager::clearCachedDefinitions), to be fair, the access handler should also be reset then.
should we go with revert revision for consistency, that all those operations contain the revision keyword?
url access checking is rather slow, as every access check is basically a route match, that could hurt quite a bit on a page with 50 revisions. also camelCase variables again.
administer nodes vs bypass node access is such a mess :-/ nothing else use administer nodes on the entity access level, only for specific fields. but this isn't the place to unify that.
we will have to get rid of this, this method is only for deprecations that are out of our control, aka vendor deprecations.
That means we can't deprecate the access checks, only specific methods. Been there before and had to revert that again.
Comment #54
jibranThis addresses #53.
Comment #56
jibranReverted #53.6. We need that for patch to pass. It includes patch from #2730631-140: Upcast node and node_revision parameters of node revision routes.
Comment #57
jibranBetter title. Also created change record as well https://www.drupal.org/node/3161210.
Comment #58
xjmComment #59
berdirHm, this is still blocked on #2730631: Upcast node and node_revision parameters of node revision routes?
Comment #60
lukusLooks like that issue has now passed.
Comment #62
nevergone(And now?)
Comment #63
aaronmchaleStill blocked on #2730631: Upcast node and node_revision parameters of node revision routes, we need that to be committed first, once that's done the patch here needs to be updated to remove the code brought in from that issue.
Comment #64
smustgrave commentedQuestion is this ticket suppose to add a revisions UI to blocks and media?
Comment #65
aaronmchale@smustgrave's question was asked and answered in #2350939-160: Implement a generic revision UI.
Comment #66
nwom commentedThis appears to need a re-roll:
Here is a re-roll of #56 against 9.2.x which includes
#2730631-140as well.Comment #67
ravi.shankar commentedFixed custom command fails of patch #66.
Comment #68
ravi.shankar commentedFixed custom command fails of patch #67.
Comment #70
ravi.shankar commentedFixed failed the test of patch #68.
Comment #72
jibranRerolled #56 after #2730631: Upcast node and node_revision parameters of node revision routes.
Comment #73
dpiUnassigning for now.
Comment #75
jibranThis needs to be updated as per standard and maybe deprecation tests as well.
Comment #76
jibranUpdated IS.
Updated existing change record.
Added new change record.
Fixed the fails in #72.
Addressed #75.
Deprecated
\Drupal\node\Access\NodeRevisionAccessCheck::__construct()and\Drupal\media\Access\MediaRevisionAccessCheck::__construct()as well as https://www.drupal.org/project/media_revisions_ui is extending that class and overriding the method.Comment #78
jibranUpdated one too many things.
Comment #79
akhildev.cs commentedhi,
sorry, I try to apply this patch #78 on 9.3.x.dev. The patch failed to apply.
I have attached a screenshot.
Comment #80
jibranIt seems like your 9.3.x branch is not up to date. Please pull down all the latest changes to apply the patch.
Comment #81
akhildev.cs commentedhi jibran,
you are right, my 9.3.x branch is behind
i am sorry for that.
now the patch applied successfully.
Comment #82
berdirmentioned that in an earlier review, we need to try and deprecate it in a way so that only actual usages trigger the deprecation.
Adding exceptions pretty much defeats the purpose of having deprecations as they will not be reported then in tests. AFAIK we should only use that for third party deprecations that we can't resolve yet.
It's not quite the same but try pudding a deprecation directly in the access implementation method. we might not catch some uses then. Although we could possibly do both? http://grep.xnddx.ru/search?text=NodeRevisionAccessCheck shows some subclases and direct calls to that.
This is also hiding some usages that we have in core right now, specifically the jsonapi module, we need to update that to use the new entity operations instead of injecting the node service.
Comment #83
jibranRemoved the deprecation exceptions. Update tests and JSON:API to use updated logic.
Comment #85
berdirI think the cache per permissions and add cacheable dependency can go, if that is relevant then it's the responsibility of the access handler to add that. In fact, I think you can remove the whole switch here, if you see below the default is just saying that it's not supported. So any existing entity type that is not implementing the operations yet will simply continue to not allow access as that is the default.
Lots of deprecations now because of the service, you need to remove the @deprecated there for now. That said, I seem to remember an issue about improving the router access logic to only call service if necessary which would allow to do this.
Comment #86
jibranUpdated logic in
EntityAccessCheckerYeah, it is either this or update the access logic.
\Drupal\Core\Access\CheckProvider::loadCheck()is the culprit here.$check = $this->container->get($service_id);triggers deprecation exception. There is no reason at all to create an instance here. If we can get the service definition then we can check all the same things. It will probably save a lot of time as well. I don't think we can get service definition from container unless we add a getter or use reflection. :PComment #87
jibranDo we need a third change notice to announce new revision operations?
'view all revisions'to view individual revision or revision listing page.'view revision'to view individual revision'revert revision'to revert revision'delete revision'to delete revisionOr NodeRevisionAccessCheck and MediaRevisionAccessCheck are deprecated should cover it?
Comment #88
berdirFound the issue I was thinking of: #3183036: Don't instantiate access checkers not used by any route
Comment #90
aaronmchale@jibran in #87:
Personally I'd argue that the current change record is enough for now, because:
Comment #91
jibran@AaronMcHale good point. I think the word generic in the title confused me. :D
This might fix some fails.
DIfference between self and static just in case:
Comment #93
aaronmchaleFrom 98 fails to only 3, I think your theory worked there @jibran :D
Comment #94
jibranTurns out JSON:API changes are not straightforward. In
\Drupal\Tests\jsonapi\Functional\ResourceTestBase::testRevisions()we haveWhich tests
\Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer::enhance()specifically.\Drupal\jsonapi\ResourceType\ResourceTypeRepository::createResourceType()uses\Drupal\jsonapi\ResourceType\ResourceTypeRepository::isVersionableResourceType()to makes sure thatnothing is supported other than media and nodes. I tried to write a test for
\Drupal\Tests\jsonapi\Functional\EntityTestRevTestto update\Drupal\Tests\jsonapi\Functional\ResourceTestBase::testRevisions()and it became very clear that changes in JSON:API should be done in its own issue.Given all that I'm going to keep changes in JSON:API to a minimum.
I have re-added the default statement.
Comment #96
berdirThat's fair, I've been in the same situation several times with those generic rest/jsonapi tests. They are certainly fancy, but their extensibility is built on top of how things work right now, and changing those things can be painful. Lets do a follow-up.
The last test makes sense, the generic logic depends on node type settings and adds the cache tag for that, so that's technically required (although I'm always on the fence on the overhead of checking that cache tag on every request vs invalidating a common cache tag, as that setting basically changes never on a running site).
do we need BC for those properties? We could use the trait that we added for entity manager? but I think json api also likes to mark a lot of its classes as internal, if that's here then fine to remove I guess.
do we really gain something from making this random? it's not even used, and this isn't even a generic entity test, we know that it would be media? Took me a few seconds to understand why we do this.
so one thing here is a bit tricky. the entity access control handler does static caching, that means if you implement something that would really vary between revisions, for whatever reason, then doing multiple access checks in the same request for different revisions will return wrong results. And we do that for the revision overview page.
I suppose the static cache could detect a non-default revision and in that case use the revision id for the static cache?
Comment #97
kristiaanvandeneyndeWhy should the admin permission not override this? Isn't that the whole purpose of an admin permission?
I was just reviewing the access checks in core and here because I've been sponsored to add revision support to the Group module and I was trying to understand the access checks behind the revision UI so I could adjust them to Group's architecture. While doing so I found that the "meat" of the access checks in core and here seem a bit rough to understand at first sight so I tried to rewrite it and came up with this instead (see below). Would now perhaps be a good time to adopt similar changes here. If tests remain green, it should be fine, no?
I didn't bother to swap out the group stuff because I think all in all it's still pretty easy to read.
Comment #98
jibranRE: #96
\Drupal\Core\Entity\EntityAccessControlHandler::access()?PS: Updating
core/modules/jsonapi/tests/src/Functional/ResourceTestBase.phpis so irritating.Comment #99
berdir3. Ha, you are right. I completely forgot that we already have that. All good then. That was actually a security issue: "Entity access bypass for entities that do not have UUIDs or have protected revisions - Access Bypass - Critical - Drupal 8 - CVE-2017-6925"
I suspect Wim isn't going to like this :)
Maybe we can add a method like getExtraRevisionCacheTags() or something, and then the node subclass can override that?
That said, are we sure that Media shouldn't have that too? (update: see below)
this is actually the entity *type* id, so it should be 'media', not 1.
this is interesting, actually.
the comment could be better, but if check the access below, is that we have "access" logic about multiple revisions. And that we don't allow access to the revision overview if there aren't at least two revisions.
That does make sense and is how it works now, so we need something for it. But is it really *entity access* logic? should we really have that complexity in here?
It's not so much about access but that we don't want to show the revisions tab/page if you don't have revisions.
That said, this has actually been changed for nodes a while ago: #808730: Show the Revisions tab/page even when only one revision exists.. And I guess that's why only node has the node type cache tag and not media.
So.. not sure what to do. This does not seem like a good time to refactor that behavior. I'm glad at least that node is different, because node would have been even more complicated (with the much earlier bypass permission for example).
At least we should have a follow-up to align media on node? And how should the generic entity access look like? Right now, it doesn't care at all. Should we have at least the logic about not being able to delete the default revision as default entity access logic? Because if not, the only thing we are standardizing here is the those entity operations exist, but we can't expect the default logic to be sufficient for a generic revision UI.
I guess the minimum is to update the comment to explain the why, not just what.
Comment #100
kristiaanvandeneyndeAgreed, this could be a simple route access check
_has_revisions: TRUEor something like that.Comment #101
larowlanComment #102
acbramley commentedFixes #99 2 and 3.
100% agree, this should be done in a separate issue. Also agree it doesn't seem like logic that should exist in an access control handler.
Still need to fix #99.1 but not entirely sure how to go about it, will continue to try and dig into it.
Interdiff may be a bit wonky because I manually changed it due to a partial re-roll.
Comment #103
jibranHehe, thanks @acbramley for updating the patch. 30 days ago I started fixing #99.1 and ran out of steam so good luck 🙂. I can add my WIP patch if that helps?
Comment #104
acbramley commented@jibran that's fine, I'm just running tests locally and if that passes will upload what I've got
Comment #105
acbramley commentedFixes #99.1
Comment #106
acbramley commentedWoops!
Comment #107
jibranThere are a few after
// Test relationship responses.which need update see interdiff in #98 https://www.drupal.org/files/issues/2021-06-05/interdiff-440182.txt.Comment #108
acbramley commentedFixes the remaining instance of checks.
Comment #109
acbramley commentedFixes linting
Comment #110
jibranNice work @acbramley.
Now that #3183036: Don't instantiate access checkers not used by any route is fixed we can re-add these service depreciations.
Comment #111
acbramley commentedAdds
deprecatedkeys back.Comment #112
jibranThese URLs should be updated to change the record URL https://www.drupal.org/node/3161210.
Comment #113
acbramley commentedFixes #112
Comment #114
anybodyHi @acbramley thanks a lot for your commit! Great work!!
Is there still anything open you know from the previous comments or ready to be community-reviewed?
Reading through the comments I only found left:
Is there already an issue for that part you know about?
Can #2350939: Implement a generic revision UI be unblocked on THIS issue or does it also depend on that refactoring? What do you think?
Comment #115
jibranWe also need a follow up for #94 and #96.0, an issue which will add revision support for non-node and non-media entities in JSON:API.
The next steps here are to get a review from the API-First team and entity maintainer.
#2350939: Implement a generic revision UI is blocked on this issue but not on any other potential follow-ups.
Comment #116
larowlanCorrect me if I'm wrong but does this mean that if you pass the working-copy flag to JsonAPI for a media entity that has only ever had one revision, but is still a draft - that you won't be granted access - even if you should be able to view revisions?
If so, I think that means we might need to do the split here.
i.e this bit
From Berdir's review.
If so, I think this means we probably want to decouple 'showing the revisions tab' from 'view all revisions' access.
I think the simplest form is to make media align with Node, and always show the tab if you have permission, regardless of the number of revisions - assuming I understand the question 😹
We'd need to include that in the change record too.
Comment #117
larowlanAdded #3225953: Add generic entity-type test coverage for JSON:API and revisions for #115
So I think that means #114/ #116 (the same thing) is the only item left here?
Comment #118
bbralaTo be honest i'm not completely sure of all the implication this has for jsonapi, still learning some of the internals of jsonapi. I'll also ping the other maintainers to see if one of them can have a look.
The point @larowan makes in #116 is a valid conceirn, but i cannot address it.
Shouldn't this merge the parent cache tags also? I know it isn't usefull right now, but should we add perhaps add some more generic cache tags this might break. Strategy could ofcourse be adding that when we need to instead which might just be the safer option for the current set of changes.
Is it OK to start doing this now? Currently core doesn't really use array unpacking like this, but with
list(). I did make an issue regarding that a bit back #3222769: [November 8, 2021] Replace all list (array destructuring) assignment to the array syntax.Comment #119
bbralaI talked to @e0ipso on slack about this issue. Other than the fact we are quite exscited to start expanding revision support in json:api after this he did raise a conceirn.
When i look at the changes it seems we should be O.K. but i think it's important to log it here also.
Comment #120
aaronmchaleFrom a UX perspective, I think this is fine. We already have an existing pattern in core - in the form of how Node behaves - and this issue probably isn't the right place to decide whether we should diverge from that.
For instance, you could make the argument that it's better to always show the tab because then it appears consistently, even if there's only one revision; We don't want to start introducing an inconsistent behaviour which leads to the user wondering: "why is there no revision tab here, is something broken", or "why can't my keyboard shortcut or screen reader find the revision tab?".
So let's just keep it simple and have Node and Media behave in the same way Node currently does, then if we want to discuss it further, we can open a follow-up issue to look at whether we want to change this behaviour.
Thanks,
-Aaron
Comment #121
berdir> Meaning there are no permissions for revisions at the entity level now:
We have the "view all revisions" operation, so I think that is covered. We don't need a global API I think (which does not exist for entity listings either), as there is no built-in use case for that. You can create a view of all revisions, but then you can also configure permissions there directly.
I think the jsonapi scope is challenging because we'd be expanding the current hardcoded support for node/media to support all entity types that implementation revision access. Beside the actual change itself, that specifically has relatively complex implications for the very generic and abstracted jsonapi test suite, so we likely need to introduce new methods on the base class to indicate whether the revision UI should be tested, what kind of access and cacheability metadata is expected and so on. But we can push all that to a follow-up and limit the support for now still to node/media with minimal test changes.
> So let's just keep it simple and have Node and Media behave in the same way Node currently does, then if we want to discuss it further, we can open a follow-up issue to look at whether we want to change this behaviour.
Note: Fully agree with this decision, but from a technical perspective, this isn't actually that "simple", as it is changing the current behavior of media entities. So it wouldn't really make sense to change it now and then open a follow-up issue to discuss if we really want to make that change :)
Comment #122
aaronmchaleAh yes that's a fair point :) My thinking was mostly on how this would currently impact the Node Revision UI, since Media doesn't have an actual UI for revisions, and likely won't until #2350939: Implement a generic revision UI is done, which if there's a desire to discuss it, should give us time to discuss and agree a pattern for the generic UI; But of course the access checking does not just impact the UI, as is being discussed here it would also impact JSON:API.
So, if I were to make a decision now for this issue and for the future, in my opinion let's not involve checking the number of revisions in the access check. Even if there's one or no revisions, at least having an interface that tells you that is better than having no interface and having to make a guess based on the fact the interface decided not to show itself - like some magic geni in a bottle :D
Also, from a caching perspective, I can only assume there's some performance advantages to not having a the "Revision" tab be conditional on how many revisions are shown. No idea if that is even a consideration for the caching system (in this case for Nodes), but if it is, then surely that's a win because then there's less variations.
Thanks,
-Aaron
Comment #123
aaronmchaleConveniently, this week's Usability meeting has just taken place, which gave me the opportunity to get wider feedback on whether the revision UI should be conditional on whether there is more than one revision.
After discussing the problem and my thoughts at #3225198: Drupal Usability Meeting 2021-07-30, there was unanimous agreement that the revision UI should not be conditional on the number of revisions. For all the reasons I previously stated, but an interesting point around training users and documentation was mentioned, where it is easier if the tab was not conditional.
This means we now have sound footing for that approach going forward.
Thanks,
-Aaron
Comment #124
acbramley commentedSo while we all agree on the fact that the revisions tab requires a refactor, I'm not sure if we've decided if it needs to be done in this issue or not? It seems like there's pros/cons to either option...
EDIT: Confirmed with @larowlan that the consensus seems to be that we should remove that logic entirely in this issue. I can work on that this afternoon/tomorrow.
Comment #125
larowlanSo looking into this further, I was off-track.
The access is not to show/hide the list of revisions.
Its to show a single revision.
i.e. you can't view a revision of a media entity, if there's only one revision.
I think that doesn't make that much sense either, but happy to wrong if there's a stated reason for it.
If you re-read @AaronMcHale's comment at #123 it seems like the same logic would apply.
So for clarity - in HEAD you can only access 'media/{media}/revisions/{media_revision_id}/view' if there is at least 2 revisions.
This also applies to node (there is similar logic in
\Drupal\node\Access\NodeRevisionAccessCheck::checkAccessMy guess is that it was copied from node to media.
But this is limiting for JSON:API because you can't view a draft if there's only a draft (And no other revisions).
So I think that is also another reason to remove that logic.
Also, I think if we agree to make that change, then we should spin off another issue, postpone this on that 😢 and fix it in isolation.
@AaronMcHale do you agree that removing that limitation makes sense?
Comment #126
larowlanThe 'must be two revisions' logic was added in #201536: Document menu_get_object() - re-reading that there's no mention of why that was added.
But luckily, @catch, @chx and @gabor are all still active, I'll ping them.
Comment #127
larowlanActually, I'm wrong - that logic came from #7582: Put revisions in their own table - yep, a four digit nid
Feels even less likely we need to retain it now if it's been there since the first patch that added a separate table for node revisions.
Comment #128
acbramley commentedAnd the media logic was copied in #2885486: Media entity is revisionable but doesn't have a revision link template
Comment #129
catchWe removed the node logic in #808730: Show the Revisions tab/page even when only one revision exists. - it took eight years, which was long enough for media to have copied the logic in the meantime.
Comment #130
aaronmchaleYes I would completely agree with that, and was also the unanimous opinion of the UX meeting.
Comment #131
acbramley commented@catch just to confirm - this is regarding revision view access, not the revision list (media doesn't even have one in core).
I've split this out into #3226588: Allow revision view access for node and media when there is only one revision and will work on this today, this issue should probably now be blocked on it?
Comment #132
acbramley commentedSorry, just caught up on the slack thread. Have closed the above and uploaded a patch to #3226487: Always allow access to revisions based on access/permissions, not on the count of revisions
Comment #133
aaronmchaleProgress! #3226487: Always allow access to revisions based on access/permissions, not on the count of revisions has been committed! I believe that means the patch here will need to be rerolled.
Comment #134
acbramley commentedWorking on the re-roll
Comment #135
acbramley commentedUnfortunately no interdiff here, I've tried my best to keep the changes 1-1 with what we did in #3226487: Always allow access to revisions based on access/permissions, not on the count of revisions but the 2 access control handlers will need another good review.
Comment #136
acbramley commentedComment #138
acbramley commentedFixes the tests and adds back the check on
view all revisionsoperation. We need to cachePerPermissions there too because we check for permissions beforehand.Comment #139
berdir#3183036: Don't instantiate access checkers not used by any route landed, so I think this is no longer true and we can simplify this?
We can then possibly also skip the @trigger_error() calls in the methods because we can be certain that the deprecation was already triggered if used.
We're getting into personal preference territory again where I know I have different opinions than some others, but this is IMHO hard to read. I much prefer having separate statements with local variables than such multi-line statements.
I know it's mostly copied, but I'd suggest a $media_storage local variable, similar to the original code that had that as a property (I do prefer using the entity type manager property!).
Also, the inlined access() checks mean we lose the cacheability info from those checks. I believe we could write it like this instead:
I believe this is the equivalent, does merge cacheablity and is easier to read?
Same for node of course.
the example in the comment is outdated. I'm also not sure if that example is really needed. It seems to mix a routing definition with php code and it seems pretty straight-forward to me?
also, is this using a camelCase variable name on purpose? afaik it is technically allowed but only if the whole file uses that?
do we really still need all those explicit access checks? doesn't $node->access('revert revision') cover them?
this is also missing snake and camel case in the same function.
wondering a bit why both have existing test coverage that we are converting and a new unit test? do we need both? if the unit test is complete, could we remove the functional test, maybe in a follow-up? The existing test could also explicitly become a legacy test here to test the BC layer in the old access check? Those deprecation messages do not seem to be tested yet, only the constructors of the access handlers.
Comment #140
aaronmchaleAgree with @Berdir in comment #139.2, that is hard to understand what is going on, let's keep it readable please.
Comment #141
acbramley commentedTLDR; This should address all of #139 except 6.
Comment #143
berdirInteresting, I expected test fails due to deprecation messages, but this looks like we are missing permission checks fo revert/delete revision operations?
Comment #144
berdirMy guess is on administer nodes, which is a very weird permission with an unclear scope and various usages that are wrong leftovers after the bypass permission was split off. Do the tests actually click on those links? Could that be broken on HEAD? that the links are visible but lead to access denied?
Comment #145
kristiaanvandeneyndeYou've already stripped them, but another emphasis on code like this:
Please don't do this, it completely kills access modules' ability to change the outcome of this check. Ideally, everything should be passed on to the code in $node->access() and all permissions should be checked there. If we need to change things up, we can do so by simply implementing hook_entity_access_alter()
Comment #146
acbramley commentedThe first failure (in
LayoutBuilderContentModerationIntegrationTest) sets up a user that has therevert bundle_with_section_field revisionspermission, so yeah it looks like we're missing permission checks in the access control handler. Will be looking into it today.Comment #147
acbramley commentedNope! We just needed to check the revision itself, not the node. This was breaking things because it was being short circuited by the fact that it was always checking revert/delete access on the default revision.
Comment #148
berdirAh, that does make sense, nice catch. The previous code was wrong then as well, we just didn't notice it as the hardcoded permission checks were sufficient.
Checking every revision is quite an overhead, but I think there's no way around that. This would allow use cases like preventing delete access on certain revisions which was not possible before.
I think there are only two things left?
1) Decide what to do with the existing and new test coverage (my point 6). Still a bit unsure about that.
2) Test coverage of the BC layers of the old access checks.
I still think that making the existing test methods legacy and check for the deprecation message might solve both problems at once. we would need to make only minimal changes to that existing test and could combine it with a todo/follow-up issue to decide if the test should be rewritten or removed when we remove the BC layer in d10? Not sure if it will be accepted by a core committer if we don't make that decision now though.
Comment #149
bnjmnmUn-checkmarking default issue credit status for #79 and #81 as they are just screenshots of git apply.
Comment #150
mstrelan commentedThis patch reverts the changes to NodeRevisionPermissionsTest, marks it as @legacy and checks for deprecations. Is that all that was needed in #148?
Comment #151
bbralaIn the last hour I've gone through the code and tested locally. This patch seems clean and finished.
I think #150 does address the 2 things left in #148. And making an issue to follow up that will remove the deprecated code in d10 and decide there how we will handle the old test code. We can make this issue when this lands.
It would be really awesome if we can get this in. And since I see no issues on the code, and there is no open feedback afaik. Settings this RTBC and crossing my fingers :)
Comment #152
larowlanJust one observation left for me. The amount of changes here are getting smaller and smaller, nice.
Fine to keep self RTBC with this change
This is only going into 9.3, protected constants where introduced in PHP 7.1 - so we can make this protected - https://www.php.net/manual/en/language.oop5.visibility.php#language.oop5...
We can remove the comment then.
Comment #153
larowlanSaving issue credits
Comment #154
kim.pepperFixes for #152
Comment #155
larowlanAdded a release notes snippet
Comment #156
larowlanComment #158
larowlanLooks to be a random fail, re-queued
Comment #160
larowlanRandom fail, back to RTBC from above
Comment #161
larowlanCommitted 1190672 and pushed to 9.3.x. Thanks!
Comment #162
jibranThank you everyone for getting it across the finish line.
Just a fun fact Lee created and fixed the issue with zero patches uploaded :D.
Also published https://www.drupal.org/node/3214171 as it is about code changes.
Comment #163
lukusAmazing work!
Comment #164
aaronmchaleGreat to see this get committed, now onto #2350939: Implement a generic revision UI, hopefully we can get that done in time for 9.4/10.0!
Comment #165
nevergone@AaronMcHale 9.3.0?
Comment #166
darvanen@nevergone the alpha deadline for 9.3 has just passed which means all new features, like this one, will need to go into 9.4/10.0.
Comment #167
larowlanThis will be in 9.3.0-alpha1 - @darvanen, it was fixed before the alpha deadline
Comment #168
darvanenCorrect, I'm pretty sure @nevergone was asking about #2350939: Implement a generic revision UI per #164 :)
But yeah, my comment was incredibly unclear, sorry about that.
Comment #169
larowlanAh sorry, I missed that, thanks
Comment #170
aaronmchaleYep, we're definitely not going to get #2350939: Implement a generic revision UI into 9.3 now, as nice as it would be to have ;)
Comment #172
jibranSorry for the noise. Here is a patch for 9.2.x. if someone needs it.