Problem/Motivation
We have a nice media library in core. We also have a nice content moderation/workflow system in core.
We also have no real, solid idea how these two systems interact with each other. This is a pretty large blind spot in the media system and its test coverage.
Proposed resolution
- Implement a media specific version of the 'Published status or admin user' views filter, to allow unpublished items to be filtered for users that do not have access to these items.
- Write a test to prove the content moderation integration works as expected in the media library
- Install media + media library + content_moderation.
- Install the 'Editoral workflow'
- Configure the Editorial workflow to apply to a new image media type.
- Configure a media field in an article node type.
- Create 3 image media items. Put one in the archived content moderation state, one in draft, one in published.
- User 1 should be able to see all three when viewing the media library.
- User 2 (with admin permissions assigned) should be able to see all three when viewing the media library.
- User 3 (with only View media) should be able to see only the published one when viewing the media library.
- User 4 (with only View media + View own unpublished media) should be able to see only the published one when viewing the media library.
- Repeat previous test case, but make all three media items owned by user 4, now they should be able to see all three.
Remaining tasks
Write patchReviewCommit
User interface changes
Added a 'Published status or admin user' filter for media based views.
API changes
None.
Data model changes
None.
Release notes snippet
The Media Library view is now integrated with Content Moderation,. The view will only list unpublished media items to users that have explicit permission to access them. This is accomplished via the addition of a Media-specific "Published status or admin user" filter, which is also available to any views displaying media entities. Note that this may result in unpublished media entities no longer being visible to users that do not have permission to access them. Their permissions should be adjusted if they continue to need access.
Comment | File | Size | Author |
---|---|---|---|
#72 | 2969678-72.patch | 755 bytes | oknate |
#71 | sqlite.png | 143.81 KB | oknate |
#71 | mysql.png | 137.18 KB | oknate |
#67 | 2969678-66.patch | 38.21 KB | seanB |
#67 | interdiff-63-66.txt | 1.38 KB | seanB |
Comments
Comment #2
marcoscanoThinking on a 80/20 mindset, I wonder how many sites really have media items "moderatable", instead of having their host entities' revisions pointing to different media items in their workflows.
I personally think most sites will have the host entity being moderated. If that's the case, and considering that this is just a view, maybe these sites can just remove the published field from the view manually?
Trying to solve this automatically could become tricky, once content moderation is set per bundle (media type), but in the view we are showing several types. So the published field could make sense for some media types but not for others, in some scenarios...
Comment #3
phenaproximaThe blocker is in.
Comment #6
phenaproximaAfter discussing this issue with @seanb, @xjm, and Wim Leers, I think we need to retitle and rescope this.
Integrating Media with Content Moderation is, right now, a big question mark. It's not clear what the 80% use cases/user stories would be, and there are many "unknown unknowns". We simply have not extensively tested media entities with moderation, so we don't know what works and what doesn't. Without knowing that, we have no way of knowing what, if anything, is important to address in this space.
So this issue should, for now, be a meta-issue in which we test Media with Content Moderation and discover problems. Then, we can organize, prioritize, and fix those problems.
Comment #7
phenaproximaComment #8
phenaproximaUpdated the IS and tagging for product manager review to determine the next steps here.
Comment #9
Wim LeersTo clarify: per #2834729-169: [META] Roadmap to stabilize Media Library, release manager @xjm has indicated this is a must-have.
Comment #10
pavlosdan CreditAttribution: pavlosdan as a volunteer commentedHow is the 80/20 determined?
We are using Content Moderation for our Media Types as our Marketing and Comms department requires strict control of what images get published (danger of getting sued if someone just lifts a stock image off of google for example). This is done separately to the content as usually M&C doesn't have context on what a media item will be used on. They only need to vet and check the image.
Similarly Documents usually need to be moderated before they are approved to be added on some content or shown in the "Approved Media Library" for regulatory and compliance reasons.
One of the pain points right now is that Media are not handling revisions the same way as nodes do. (e.g missing the revisions tab).
Comment #11
marcoscano#2987911: Make media compatible with content moderation points out some parts of the media system that don't seem to be ready for content-moderation-prime-time yet
Comment #12
phenaproximaThanks, @marcoscano! I have added that to #2825215: Media initiative: Roadmap since the bugs mentioned in that issue are not Media Library-specific. :)
Comment #13
xjmThanks @pavlosdan! Those do sound like issues we should identify here.
To add to #9: The must-have scope of this issue is to identify and file issues for whatever bugs, incompatibilities, etc. we have. Once we do that testing, then we'll go back through the issue list and sort whatever specific issues are identified into must/should/could blockers for the stable release. I imagine the issues in #10 are probably major bugs.
Comment #14
Wim LeersOn a Media-related meeting just now @seanB and @phenaproxima said they aren't sure that something even needs to change as far as
media_library.module
is concerned (this issue is specifically aboutmedia_library.module
, not aboutmedia.module
).@seanB indicated that the view used by the Media Library simply respects the
published
flag, and that that is what Content Moderation is using to ensure only authorized users are able to see the appropriateMedia
entities. Hence that should also work in the Media Library.If this is true — and I sure hope it is! — then it should be easy to write test coverage proving this is the case:
standard
+content_moderation
. This also results incore/profiles/standard/config/optional/workflows.workflow.editorial.yml
getting installed.archived
content moderation state, one indraft
, one inpublished
.1
should be able to see all three when viewing the media library.2
(with admin permissions assigned) should be able to see all three when viewing the media library.3
(with onlyView media
) should be able to see only thepublished
one when viewing the media library.4
(with onlyView media
+View own unpublished media
) should be able to see only thepublished
one when viewing the media library.Note: IDK exactly what the difference in effect is between
draft
andarchived
. I think thatdraft
means a forward (non-default) revision is created, but Views lists only default revisions of entities. So that'd mean that thedraft
entity would be visible to anybody with theView media
permission. Content Moderation expert should chime in.If a Content Moderation expert (ideally maintainer) confirms the above testing scenarios are sufficient, then the tests will either show we have no work to do (like @seanB thinks 🌞) or will show exactly which scenarios are failing and hence where the bugs are hiding 🤓
Comment #15
seanBJust wrote the tests for the scenario's of #14. Some remarks.
Attached is a combined patch with #3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label to prove it works, and a patch with just the content moderation test.
Comment #16
seanBFor now this is blocked on #3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label.
Comment #18
seanBAhh, I should have used the latest patch in #3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label. Retry.
Comment #20
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented#2987911: Make media compatible with content moderation contains a few issues with media generally that should probably be addressed, but as far as I can see these don't really relate to the media library.
In general content moderation manages revisions on two axis: the publishing status and the default/non-default status of a revision. If the user facing views/lists/rendering of media items generally load and use the default revision, then in theory as long as you are respecting your own published/unpublished semantics, you shouldn't need any kind of special integration or consideration for content moderation. In all workflow states, you are either getting a published or unpublished revision, so for the purposes of display unless you are loading non-default revisions you don't need to be concerned by defaultness.
As far as updating revisions, I would assume updates are made via an entity form? Content moderation switches some of the routing upcasting to load the latest revision of an entity for entity forms, but it might be worth asserting that if you create a pending revision via a draft, that reloading or visiting an interface to update media items, the draft content is still loaded as the starting point. I can't really see any scenarios like that, but I'm not sure if that's actually within the scope of the media library?
(Note, 'draft' state content is only placed into a pending revision if there was a previously published revision, so it's not enough to simply create draft content when testing the above)
In terms of actually providing an experience for users of the CMS to view revisions and possibly see a list of in-flight moderated entities, nodes have the following:
So in short, #10 is correct, but this isn't necessarily a content moderation integration problem. Even without content moderation, media entities are missing a way to view past revisions.
Comment #21
oknateI have reviewed the test and it looks great. I don't see any issues. If it were checking only visible rows, it would pass and prove that media library respects the accessibility of media based on user permissions. As it's checking using raw assertions, it's blocked on #3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label. I don't have anything new to add other than to saw I reviewed the test and it looks great.
Comment #22
effulgentsia CreditAttribution: effulgentsia at Acquia commented#3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label is about handling media access in general, but for purposes of this issue (integration with Content Moderation), we only need to handle the special case of Published, right?
In which case, can't we solve it in exactly the same way that the
/admin/content
View does: by adding a filter for "Published status or admin user"? Grep HEAD forstatus_extra
to see the different code pieces needed for that, including theDrupal\node\Plugin\views\filter\Status
class. I think for the purposes of this issue, maybe we can just duplicate all that stuff to Media? As a follow-up, maybe we can genericize the code for other entity types to benefit from too?Comment #23
seanBIn which case, can't we solve it in exactly the same way that the /admin/content View does: by adding a filter for "Published status or admin user"? Grep HEAD for status_extra to see the different code pieces needed for that, including the Drupal\node\Plugin\views\filter\Status class. I think for the purposes of this issue, maybe we can just duplicate all that stuff to Media? As a follow-up, maybe we can genericize the code for other entity types to benefit from too?
That would solve it for admin users, but saving an unpublished media item would then only be available for admin users. I guess that could be very confusing, since users could have permissions to view their own unpublished media?
Specially since media also doesn't have a canonical route by default, which means a view is the only way to find and edit existing media.
Comment #24
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThe label of the filter is misleading. Although it says "or admin user", what it really means is "or the user has permission to the unpublished media". The query in
Drupal\node\Plugin\views\filter\Status
includes checks for "view own unpublished content" and "view any unpublished content".Comment #25
seanBMy apologies, that does seems to solve it. I guess this would remove the dependency on the other issues. Thanks!
Comment #26
seanBThis implements the 'Published status or admin user' filter for media. It makes the tests from #14 pass and is a much nicer solution compared to #3063216: Inaccessible Media entities still have rows generated for them in the Media Library view, containing form wrapper markup *and* the media label.
For now I skipped an upgrade path for existing media / media_library views. I feel adding this to existing sites should be a site builder decision and could potentially cause rows to be removed unexpectedly. Views are tricky and existing sites could be depending on the existing behaviour.
Comment #28
seanBOk, this should hopefully fix the tests.
Comment #30
seanBSome minor doc improvements and also updated the content moderation test name and namespace.
Comment #31
seanBSorry, the last interdiff was not correct, here is the correct interdiff for #30.
Comment #32
phenaproximaI think this looks really good overall. The test coverage unambiguously proves that the views respect the publishing status (with administrative superpowers), which is controlled by Content Moderation.
This is interesting, I wonder why it changed?
Why were these (and other) assertions removed?
Pro tip: We can call $this->getSession()->reload() to avoid repeating the URL. (Not a big deal, obviously.)
So I see our changes to this view are causing the filters' configuration to be overridden from the defaults. Why is that?
Also, much of the diff of changes to this view looks very chaotic. I suspect (assume, really) that's actually a red herring; chances are, the filters have been re-ordered, causing the diff to appear crazier than it normally would. No need to change it, just calling it out.
The @group should be media_library.
Why does this need to be tested with the Standard profile? If it's to bring in the content type and workflow, we can just use $this->drupalCreateContentType() and ContentModerationTestTrait::createEditorialWorkflow(), respectively.
Nit: There's an errant blank line here.
Couldn't we use the $this->rootUser property inherited from BrowserTestBase?
This comment is misleadingly placed; it should probably come after this chunk of code.
😍
Comment #33
phenaproximaI briefly talked to @seanB in Slack about what sort of update path we want to provide here, if any. I don't think such an update (which would really just be adding the status_extra filter to our media views) would be too hard to write, but Sean rightfully pointed out that it could break existing sites which may be depending on the current, "broken", behavior. My feeling is that we should open a separate issue to talk about the update path; basically, if the product managers think it should be done, we should do it in there.
Comment #34
Wim Leers+1
Assigning to @webchick, this feels like a thing she'd be most opinionated about. Plus, she's already reviewed this issue before.
Comment #35
webchickSo, just so I’m understanding… the question being posed is…
“We are making a change to the default view to make it so that you don’t see media you shouldn’t have access to (e.g. unpublished where you do not have perms to see unpublished), and we are wondering whether to retroactively apply this to all existing sites?”
I mean. I think the answer can only be yes? Isn’t it an information disclosure vulnerability otherwise?
And if people are truly depending on broken behaviour, as @seanB frets, they can just remove the filter, no? But we’d still leave all other sites “secure by default”?
So yes, I think I would recommend the spin-off issue, unless I'm missing something here.
Comment #36
webchickComment #37
effulgentsia CreditAttribution: effulgentsia at Acquia commentedIf Content Moderation module is enabled, we also need to add a condition for
"view any unpublished content"
, just like the filter in the node namespace does. The reason is becausecontent_moderation_entity_access()
checks that permission for allEntityPublishedInterface
types, not just nodes.Comment #38
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI also don't think this needs Product Manager review anymore, especially after #36. It's a stale tag from #8.
Comment #39
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThis isn't a meta issue anymore. #30 is almost a complete fix. Just needs to incorporate the reviews since then.
Comment #40
seanB#32
1. Fixed. Not sure why it was there, works without it as well.
2. The non-admin user doesn't have permissions to view unpublished media. The default view was not using rendered entities, so the view was not preventing the user from seeing media they shouldn't.
3. Fixed.
4. Fixed. I indeed changed the order of the filters for more consistency with the content view, but that is not really necessary. Reverted the unrelated changes.
5. Fixed.
6. Fixed. Initially implemented the test as suggested in #14, but it is true we can also test without the standard profile.
7. Fixed.
8. Fixed.
9. Fixed.
10. :)
#37
Fixed and updated the content moderation test for this.
Comment #42
oknateThere are some coding standard warnings:
Comment #43
oknateFixing the coding standard warnings and fixing the config test.
Comment #45
oknateSame as the last comment. Trying this again, the last comment, the interdiff had the changes, but the patch lacked them. I must have been on the wrong branch when doing the diff against 8.8.x branch.
Comment #46
seanBDon't think I can RTBC, but thanks @oknate! I think this is ready now.
Comment #47
Wim Leers👍 This test looks excellent, and it does what I recommended in #14. It does more, actually, because I had not mentioned
view any unpublished content
in #14.@effulgentsia pointed out the need for this in #37. I think this is extremely misleading in the Content Moderation module and frankly downright dangerous. But that's how HEAD works, so … 🤷♂️
🤓 Nit: s/the/that/. Can be fixed on commit.
🤓 Übernit: s/it's/its/. Can be fixed on commit.
Comment #48
seanBFixed the nits to make it easier for committers :)
Comment #49
seanBUpdated the IS.
Comment #50
seanBComment #51
effulgentsia CreditAttribution: effulgentsia at Acquia commentedCrediting @phenaproxima and @Wim Leers for patch review.
I was torn on whether to also credit @marcoscano, @pavlosdan, and @Sam152. You all wrote very helpful comments earlier in this issue, but because they're more in the scope of #2987911: Make media compatible with content moderation, I added issue credit to you there instead of here.
Comment #53
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPushed to 8.8.x, but per #35, this needs a followup for a
post_update
function.Comment #54
xjmI'v reverted this for now. We need upgrade paths to be included in the same issue, not in followups. Thanks!
Comment #56
seanBThe first question is, do we really want an upgrade path at all and add this new filter to all media views on all existing sites?
This could mean users will all of a sudden not be able to see content they previously could, and I'm not sure if we want this. User could be relying on the existing behaviour.
Comment #57
phenaproxima#35 has already answered this, I think. Personally, I agree with it. We should add the post-update hook. We can have it return a translated string (which is something post-update hooks can do, according to the documentation of hook_post_update_NAME) explaining what was changed, and how to restore the previous behavior.
Comment #58
effulgentsia CreditAttribution: effulgentsia at Acquia commentedNo. Not all views. Just the 2 Views shipped with core and being changed by this patch. Views shipped by contrib/custom modules are the responsibility of those other modules. Views created by the end user via the UI already get a Status=1 default filter applied on them, and if the user removed it, it's their responsibility.
I'm fine with us returning text that explains what was changed. I don't think we need to explain how to restore the previous behavior. There's no legitimate reason to restore the previous behavior for these 2 Views. If someone really wants to do it for an illegitimate reason, they can, but we shouldn't communicate any text that encourages it.
Comment #59
seanBOk, added the update hooks and tests.
Comment #60
phenaproximaThank you so much for taking this on, @seanB. Your heroism knows no bounds!
Supernit: There is a space before the )) here.
Should we use RoleInterface::AUTHENTICATED_ID here?
I wonder if we might want to use the label of the view here, instead of just hard-coding 'media'. It's possible the site builder changed the label.
It would be more readable, IMHO, to do something like this, rather than repeat the very long key:
Not sure if this is in scope, but this advice is actually incorrect. If they uninstall and reinstall the module, I believe they will run into PreExistingConfigException. I guess what I'm asking is...have we actually tested this advice?
Also: do we consider this to be an error condition? If so, we should be throwing UpdateException, per the documentation at https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension...
Comment #61
phenaproximaAlso, another question -- for (site builder) simplicity and/or performance reasons, should we replace the existing status filter with status_extra, rather than have both?
If we do replace it, and the status filter has been customized in some way(s) by the site builder, should we copy that configuration into status_extra? In other words, should we maybe just take the existing status filter, do the minimum number of changes we need to do in order to convert it to status_extra, and then blow away the original filter?
I realize this would complicate the update path, but it might be more "seamless" from a site builder perspective. So, I defer to a framework manager for this one.
Comment #62
seanBAs a first response to #61:
The existing status filter is exposed and allows user to change it. The status extra filter is a hidden thing and does something similar, but different. So I think they should both be there.
I will take a look at #60 tonight.
Comment #63
seanBNew patch to address #60:
1. Fixed.
2. Fixed.
3. Fixed. I tried referring to the machine name of the view. But I guess the label would be better.
4. Fixed.
5. Just checked, removing the media library view, then uninstall/install the module will bring back the media library view as expected. So yeah, this seems to be correct. Not sure how hard the update should fail, the site is not at risk of being more/less broken by continuing the updates. I copied this from
media_library_post_update_table_display()
btw.Comment #64
phenaproximaSmall nit: The label should be placeholdered in as
%label
, not'@label'
, since it's a human-readable name.Otherwise, as far as I can tell this is good to go.
Comment #65
seanBFixed #64.
Comment #66
phenaproximaWe don't need the single quotes around %label, I think. But this is fixable on commit.
This is, as far as I can tell, done.
Comment #67
seanBNo worries, it's already end of the day here. Missed the quotes. Sorry!
Comment #69
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThe post_update functions and tests look great to me. Thanks! Pushed to 8.8.x.
Comment #70
oknateThanks!
Comment #71
oknateTests are failing in SQLite and Postgre: https://www.drupal.org/node/3060/qa
https://www.drupal.org/pift-ci-job/1417227
https://www.drupal.org/pift-ci-job/1417228
The order of the items differs. Since they are all programmatically created at the same time, the sort by 'changed' is brittle.
Mysql:
SQLite:
Comment #72
oknateHere's a patch that addressed #71. It fixes the sorting by changed date, so the expected item is in the second position.
Comment #73
phenaproximaThanks, @oknate. That should help; it's not the first time we've been burned by implicit sorting differences across databases. RTBC once green on all backends.
Comment #74
Wim LeersWe recently had to do a similar thing in JSON:API: when no explicit sort is specified, the underlying DB's default sort is used. Which is obviously different between MySQL/MariaDB, PostgreSQL and SQLite.
Comment #75
catchCommitted 2dfe9e9 and pushed to 8.8.x. Thanks!
Comment #77
catchComment #79
xjmWe can mention this in the highlights as part of what a stabilized Media Library offers, e.g. "CKEditor support, Content Moderation integration..."
Comment #80
xjmActually this needs a change record for the API addition (also, isn't the codebase missing API documentation for it?).
I think this also may need a release note since it changes the result of the view to (by design) not include media the user can access. (It does that, correct?) In the past when we've had security issues that similarly revoke access to things, we've also mentioned it in the release notes for sites that might have been relying on this behavior rather than on correct access control handling. So, let's add a release note too (if I'm correct about that).
Comment #81
xjmWe should also describe the disruption from the un-access-bypassing: Before stuff you shouldn't have access to was listed; now it's not. If your permissions were configured incorrectly, you'll want to fix that.
Comment #82
bnjmnmAdded change record draft and release notes snippet.
It may require some refining as I'm not sure what API documentation needs to be added, which was mentioned in #80. If there's something equivalent elsewhere in core I could be made aware of, that's all the reference I'll need to take care of it.
Comment #83
bnjmnmCR https://www.drupal.org/node/3088444 and release note added. Setting to Needs Review.
Comment #84
bnjmnmCreated followup to update the API documentation in the codebase #3088632: Add the Content Moderation changes from 2969678 to Media API docs, and received confirmation that CR/RN are all set. Setting back to Fixed.
Comment #85
xjmPerfect, thanks @bnjmnm!