Problem/Motivation
Proposed resolution
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Task | Novice task? | Contributor instructions | Complete? |
|---|
Problem/Motivation
While responding to a review in #3038254: Delegate media library access to the "thing" that opened the library, @Wim Leers discovered that forbidding access to media entities like this:
diff --git a/core/modules/media/src/MediaAccessControlHandler.php b/core/modules/media/src/MediaAccessControlHandler.php
index a8fdea2f63..b8ebb0181f 100644
--- a/core/modules/media/src/MediaAccessControlHandler.php
+++ b/core/modules/media/src/MediaAccessControlHandler.php
@@ -16,6 +16,7 @@ class MediaAccessControlHandler extends EntityAccessControlHandler {
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+ return AccessResult::forbidden();
if ($account->hasPermission('administer media')) {
return AccessResult::allowed()->cachePerPermissions();
}
results in a media library selection dialog looking like this (with a test media library that contains 2 images):

Unfortunately you can see in that screenshot that some per-row (per inaccessible media entity) HTML is generated that should not be generated:
<div class="media-library-item media-library-item--grid js-media-library-item js-click-to-select views-row"><div class="views-field views-field-rendered-entity"><span class="field-content media-library-item__content"></span></div><div class="views-field views-field-media-library-select-form js-click-to-select-checkbox"><span class="field-content"><div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-media-library-select-form-0 form-item-media-library-select-form-0 form-no-label">
<label for="edit-media-library-select-form-0--MFsPcY2kHNU" class="visually-hidden">Select llamaaww.PNG</label>
<input data-drupal-selector="edit-media-library-select-form-0" type="checkbox" id="edit-media-library-select-form-0--MFsPcY2kHNU" name="media_library_select_form[0]" value="4" class="form-checkbox">
</div>
</span></div></div>
Expected: an "empty result" message ideally, or at least no markup for each inaccessible media entity (every row in a view).
Actual:
- Form wrapper markup for every inaccessible media entity, hence disclosing the number of media items in the library. The actual security risk of this seems minimal at best — Drupal's sequential identifiers for content entity in general already reveals this on many sites. We don't consider this sensitive information.
- That markup also contains a
Select @labelstring, which could be highly sensitive information.
Proposed resolution
- Security hardening: ensure that no markup is generated at all for any row containing an inaccessible
Mediaentity. - An "empty result set" message or behavior, that also works when the user is denied access to all existing
Mediaentities.
And needless to say: tests for both.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Views now adds a ENTITY_TYPE_ID_access query tag to all its queries by default. This means that any implementations of hook_query_TAG_alter(), where TAG is of the pattern ENTITY_TYPE_ID_access -- for example, user_access if user accounts are being queried -- will now run on Views queries as well. Modules implementing such hooks should ensure that this change does not result in unwanted side effects.
| Comment | File | Size | Author |
|---|---|---|---|
| #50 | interdiff.txt | 1.68 KB | wim leers |
| #49 | 3063216-48.patch | 13 KB | wim leers |
| #47 | interdiff.txt | 5.41 KB | effulgentsia |
| #47 | 3063216-47.patch | 12.93 KB | effulgentsia |
| #44 | 3063216-44-PASS.patch | 7.3 KB | phenaproxima |
Comments
Comment #2
wim leersTo mitigate the disclosing of the label, this is sufficient, but it still results in wrapper markup being generated. Correct solution TBD.
Frankly, I'm surprised that
\Drupal\media_library\Plugin\views\field\MediaLibrarySelectFormeven runs at all considering this row is supposed to be inaccessible! 😨I don't know Views well enough to be able to answer this without spending many hours stepping through Views. Hopefully somebody else can answer this.
Comment #3
wim leersComment #4
phenaproximaI think we're going to need tests here :)
Comment #5
wim leersThat, and an actual solution, because #2 is merely a mitigation, not actually a solution!
(I marked this as because it needed digging deeper and possibly feedback from Views experts.)
Comment #6
wim leersTalked to @seanB, he's gonna look at this :)
Comment #7
seanbViews unfortunately does not check entity access on query or render time. We would probably need something like #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter() to fix the views query, and if we hide the row on render we would probably break the views pager (if we have 10 rows per page and then hide 4 rows for example it would look pretty weird).
I think for we should at least fix the label disclosure in the checkbox, and wait for #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter() so we can implement a better solution to hide inaccessible rows completely.
Comment #8
seanbHere is a test for it and a fix. The standards views bulk form contains the same problem, so had to fix it there as well to make the test pass for the admin/content/media overview page.
Comment #9
seanbComment #12
seanbHmm, according to
UserAccessControlHandler::checkAccess(), the anonymous user's profile can neither be viewed, updated nor deleted. Yet in the BulkFormTest we do exactly that, trying to block an anonymous user. Not sure what to do here. I think the BulkFormTest is doing something that shouldn't be possible so we need to change the test, but more opinions are very welcome.Comment #13
wim leers#7:
Right, Views only does that for
nodeentities because only for that entity type does a query access API exist.This is a known weakness in Views. The creator of a view is expected to set up the appropriate filters to ensure it only shows information that users who can access the view are allowed to see.
This is the official answer that came up while I was working on https://www.drupal.org/sa-contrib-2018-081.
In the best case, this will happen in a year. We can't wait for that. Let's add a work-around, even if it's hacky, and add a
@todopointing to that issue.#12: that seems out of scope here?
Comment #14
seanbI agree, and I think the workaround to remove the checkbox should be good enough for now. Users should try to use views filters as much as possible to prevent inaccessible rows from being shown in the mean time.
Not so sure about this. This issue is about the media library disclosing information it shouldn't. That happens via
BulkFormandMediaLibrarySelectForm. If we fix the issue in BulkForm, we have to change theBulkFormTestin the user module as well, since the test is apparently doing something that should not be possible.We could split the issue up, I'm perfectly fine with that if it helps, but both bulk forms need to be changed to fix the original information disclosure issue for the media library.
Comment #15
lendude@seanB as posted on slack, it's not doing something that should not be possible, it is a bad test, it doesn't check that the anon user isn't blocked before trying to block it.
If you change the test to:
it still passes
Comment #16
lendudeThe problem I see with the proposed change here to the generic bulkform field is that the 'view' permission is used as a 'one permission to rule them all', if you don't have 'view' access, you cannot use the entity at all. The problem is that we don't know which action the user is going to attempt, so we have no idea which action to check the permission for. Using 'view' as a catch-all seems nice, but it means the setup where you have 'edit' rights but not 'view' rights is not possible. Might make sense for most entity types, but for ALL? Not so sure.
Comment #17
hlopes commented#7, I guess you can try https://www.drupal.org/project/entity/issues/2909970 ?
Need entity module though
Comment #18
seanbAs mentioned by Lendude on Slack, the
view labeloperation could allow access to information in a views row, so that would be good to check that as well.#15
You are right, I removed the test since it doesn't actually do anything.
#16
Currently you can perform actions on entities you are not allowed to see, which is exactly the "bug" we are trying to fix. How can you edit/delete information you don't have access too? Please correct me if I'm wrong, but that doesn't really seem to make sense.
#17
Unfortunately, we can't depend on contrib code in core.
Comment #19
alexpottI think @Lendude's concerns are valid - and if we want to fix them here then we need to postpose this issue on a views bulk form issue to implement this. I think this might not be an issue for nodes because node_access is checking on the query level.
Alternatively...
Adding this feels tricky. Somethings like the awkward test added in core/modules/user/tests/src/Functional/Views/BulkFormTest.php depend on this not being there. How about we make this configurable?
Comment #20
wim leers#18++ and @Lendude++ for
view label!#19: I agree, but I also agree with the rationale laid out by @seanB in #18. I think this is a step forward, even if it's not ideal. The very last thing you write in #19 I don't quite understand: how to make what configurable how?
Comment #21
alexpott@Wim Leers we can define a new
obey_entity_view_accessoption (or whatever you want to call it) in\Drupal\views\Plugin\views\field\BulkForm::defineOptions()and make it configurable in\Drupal\views\Plugin\views\field\BulkForm::buildOptionsForm()and use it in\Drupal\views\Plugin\views\field\BulkForm::viewsForm()and then update the media library view to use this new option.Comment #22
wim leersAh, interesting! Seems reasonable.
@seanB, do you think you could create a new issue to implement @alexpott's suggestion, which would address @Lendude's concerns and would make this patch smaller?
Comment #23
seanbCreated #3070237: Bulk form for Inaccessible entities still have rows generated for them in views, containing form wrapper markup *and* the label. Rerolled on top of that.
Comment #25
wim leersLooks like a random DrupalCI failure. Retesting. I expect it will return green.
Comment #26
seanbThat was a debugging line I forgot to remove, sorry about that. Also rerolled on top of #3070237-7: Bulk form for Inaccessible entities still have rows generated for them in views, containing form wrapper markup *and* the label.
Comment #27
webchickSorry, I'm not sure which issue to comment in... my comment is actually about #3070237: Bulk form for Inaccessible entities still have rows generated for them in views, containing form wrapper markup *and* the label but I see the "backstory" in here.
@Lendude in #16 says:
"The problem I see with the proposed change here to the generic bulkform field is that the 'view' permission is used as a 'one permission to rule them all', if you don't have 'view' access, you cannot use the entity at all. The problem is that we don't know which action the user is going to attempt, so we have no idea which action to check the permission for. Using 'view' as a catch-all seems nice, but it means the setup where you have 'edit' rights but not 'view' rights is not possible. Might make sense for most entity types, but for ALL? Not so sure."
I basically agree 100% with @seanB in #18:
"Currently you can perform actions on entities you are not allowed to see, which is exactly the "bug" we are trying to fix. How can you edit/delete information you don't have access too? Please correct me if I'm wrong, but that doesn't really seem to make sense."
Indeed...
The initial premise seems invalid to me, because in order to use Views as the mechanism to edit or delete a bunch of entities, you must have access to at least view the entity label, if not anything else about the entity. Otherwise you get the very UI that we're trying to fix here, and subject yourself to random wanton data destruction by clicking things and seeing what happens. :P
@alexpott says in #19:
"I think @Lendude's concerns are valid - and if we want to fix them here then we need to postpose this issue on a views bulk form issue to implement this. I think this might not be an issue for nodes because node_access is checking on the query level."
I personally think the "if" we want to fix them, is no. If you're trying to set up some kind of highly restricted edit of entities you can't view, then custom code is just a few keystrokes away. :)
If I'm missing an obvious use case here, let me know. But at a glance this seems like a very straight-forward bug we just ought to fix, rather than adding more UI options to Views which is already a sea of UI options.
Comment #28
lendudeSo, the main flag I wanted to raise was that we are effectively adding a form of hierarchical permission here, were we have one permission that overrules a second one (if you don't have
view labelyou effectively don't getedit/delete entity).So to do that properly we would need something like #1200572: Concept of a hierarchical permission system (I am NOT proposing we postpone on that :D)
To have that within media is fine I think, that is one specific entity type, but to have that for the generic entity bulk field handler in Views should be done with a little more caution I feel. Hierarchical permissions is not a pattern that is used in core that much I think and it's not something we can enforce elsewhere without the mentioned issue landing.
I'm not saying we should block on this, but I want to at least have it noted that we making this decision.
But my suggestion would be to just fix it in media. If we fix it in views that we would have one field handler that behaves differently then all the others in this particular set up. All other fields are still going to show (including the actual title field!), just not the bulk field. See sceenie from the other issue:

I think that is just going to lead to more questions, but that is just a guess obviously.
I fully agree with @seanB in #7 that the real bug/task is #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter(), and it is not that straight-forward :)
If we add a workaround for that, maybe add a @todo that makes it clear that it is a band-aid solution waiting on a proper fix?
Comment #29
wim leersYep, the real bug here is #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter(). That keeps coming up.
To make @Lendude's point more practical: it's totally possible and even very likely that there are sites out there that have views configured where only users with elevated permissions can access the view and that's how they prevent the access bypass. Views has always taken the stance "site builders ought to know what they're doing, both in setting up the view and in choosing who can access it" — in large part because #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter() is not fixed yet.
So, as ugly and weird as it is, I also agree with @Lendude and @alexpott here, because there is a big risk here that we break existing sites' views. And not just any views. Particularly views that are used in complex workflows!
Comment #30
effulgentsia commentedI'm leaving this issue open for the questions around handling Media (and maybe other entity) access in general, but for the specific case of access to unpublished media, see #2969678-22: Integrate Media Library with Content Moderation.
Comment #31
wim leersSo … in #23 + #25 we postponed this on #3070237: Bulk form for Inaccessible entities still have rows generated for them in views, containing form wrapper markup *and* the label. But @webchick posted a comment in #27 where she basically disagrees that we need to do #3070237: Bulk form for Inaccessible entities still have rows generated for them in views, containing form wrapper markup *and* the label, or even this very issue? So changing status from to to get things going here again.
In #27 @webchick wrote:
Well … that is where I disagree. What if I as somebody on the HR team upload a file called
Factory closure announcement.pdf, make sure only HR can see it, and then a marketing person is writing something and while inserting a picture of a fluffy bunny notices this media label? Sometimes just being able to see the label is an access bypass.Comment #32
xjmBased on #31, this issue should probably have been reported privately first.
I discussed the issue with a few members of the Security Team (members of the Security Working Group and other committers on the security team), and we agreed to continue handling the issue in public. However, since there is an info disclosure/access bypass here, promoting to critical. The fact that it's a known security issue also makes it a blocker for Media Library to be stable, as per https://www.drupal.org/core/experimental#stabilizing.
Comment #33
xjm@phenaproxima and @effulgentsia also asked why this issue needed to block Media Library being stable when any view can be affected by this issue and the most complete fix would be upstream in Views. However, the Media Library is itself a view we are shipping in core, and furthermore, it's a view that's accessible to content authors. (Most of our shipped views are only available to administrators, and there are additional hardenings already in place for nodes, taxonomy, and comments.)
Comment #34
wim leersI investigated and checked when I first reported this on June 21, 2019. If I remember correctly, the policy is to have security issues in experimental modules be public. But perhaps I'm misremembering. In which case, mea culpa.
Comment #35
effulgentsia commentedIn my opinion, the premise of the issue summary is wrong.
It says that forbidding access via
MediaAccessControlHandler::checkAccess()should be respected by Views. But that's not how Views works anywhere else. For example, if you make the same kind of change toNodeAccessControlHandler::checkAccess(), it has no bearing on what's shown in the frontpage View; that View will happily show teasers of those "forbidden" nodes.For nodes, we explicitly document this in hook_node_access():
We unfortunately do not document that for other entity types (e.g., in
hook_entity_access()orhook_ENTITY_TYPE_access()). So one part of fixing this issue might be to add that to those docs.So in that case, how should a module forbid access to media entities in a way that Views, including the Media Library View, will honor? For nodes, there's the grants API that's available, but that's not available for other entity types.
In my opinion, the best option for such a module is to use the query access API provided by the Entity API module. #18 says "Unfortunately, we can't depend on contrib code in core.", but we don't have to. It's not core that's wanting to implement additional media access logic. It's a hypothetical contrib/custom module that might want to, and that module can depend on the Entity API module.
Using that API is pretty straightforward. Here's an example of a query access subscriber that a contrib/custom module can implement if it wants to forbid access to Vimeo videos:
This only works if the media entity type is assigned a query_access handler, which can be done with:
Note that that might be the wrong handler class to assign (it might have logic in it that we don't want for media entities). I opened #3086409: Provide a default query_access handler for core (maybe all?) entity types to see if Entity API can provide a better default and auto-assign it, so that all a module would need to do is implement the subscriber. But even if Entity API maintainers say no to that, and even if the above hook_entity_type_build() implementation is incorrect, a real module needing to implement query access could implement a correct
QueryAccessHandlerclass.Note that the above doesn't require any changes at all to core to work, because Entity API hooks into Views via
hook_views_query_alter(), which is invoked for all Views.However, if a module wanting to implement query access control doesn't want to do so via Entity API module, then the currently available core API for it is to implement
hook_query_media_access_alter(), which is just a special case ofhook_query_TAG_alter()with 'media_access' as the tag. The main drawback of using that API is that you're dealing with a low-level database query object, so you have to deal with adding JOINs and table and field aliases and all that cumbersome stuff. It's really really not the ideal API, which is why Entity API added the query access API and why bringing that into core via #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter() is a good idea. But, for now, it's the core API that's available.Because it's the core API that's currently available, we should fully support it, which means making sure that Views actually tags its entity queries correctly. It currently does for some entity types, but media isn't one of them. Here's a patch that adds the tag for media and the other ones. Note that I got permission from the security team to post this patch to this issue.
In my opinion, this one line fix is all that is needed to mark this issue fixed. But I'm open to people disagreeing with that. In particular, we should decide if we also want to add test coverage for it (and where, since it's not a Media Library specific fix), and if we also want to add docs for hook_ENTITY_TYPE_access() that lets people know that it's not invoked for listings, similar to how we document hook_node_access() with that information. However, a challenge with writing those docs is that we'd need to decide what to point people to: the Entity API project, or hook_query_ENTITY_TYPE_access_alter(), or neither, or both.
Comment #36
effulgentsia commentedComment #37
gabesulliceThanks for the write up @effulgentsia!
💯
Everything else you write flows logically from this.
💯
I think it would be good to have a test in
/core/views/modulesthat implementshook_query_media_access_alter().I think that, for this issue, we should update the
hook_ENTITY_TYPE_access()docs to point tohook_query_TAG_alter(), saying "see/core/views/modules/{test_to_be_written}for an example".Then, let's open a follow-up issue for adding a
hook_query_ENTITY_TYPE_access_alter()stub that we can document more thoroughly. We can add a @todo for this as well.Comment #38
phenaproximaHow does this look as an example of a hook_query_TAG_alter() that modules could implement? This has no test coverage yet, so no need to run testbot.
Comment #39
phenaproximaMoved the query alter into a formal test module, and made it generic enough to test any of core's content entity types. 🤓
Comment #40
phenaproximaHere we go, with proper, if simple, tests of Media and Block Content entities. :)
Comment #41
phenaproximaSorry about that, dumb mistake.
Comment #42
wim leersI agree with you that the premise of the issue summary was wrong, i.e. the
hook_ENTITY_TYPE_access()expectation was wrong. I am the author of it. Sorry about that 😕 That being said, I do believe it's more likely for sites to be writing custom access logic formediaentities than they would forblock_contentfor example. That's how I discovered this in the first place: I put myself in the shoes of a well-intentioned developer, who after reasonable testing (of the individual media entity) would conclude it works, but they would be wrong.Comment #43
wim leers👍 This is the only actual change. Everything else is test coverage. If I omit this line, the tests fail:
FAIL-test-only.patchfile that excludes this one line? That would make it obvious!🤓 I think
views_test_query_access_tagwould be more accurate?👍 This implements hooks responding to the query tag for two different entity types. Proving the solution is generic.
🤓
media→entities(this logic is not media-specific).👍 This proves it works for both of those entity types.
🤓 Übernit: we don't need
$account.👍 Simple and clear assertions. 🙂
Comment #44
phenaproximaviews_test_query_access, to make room for future test code related to query access checking.Comment #45
wim leers👍🚢
Comment #47
effulgentsia commented#44 looks great to me as far as test coverage goes. #37 suggested having the test implementation also be reused for docs, but I don't think what's in #44 is good for that purpose, because it assumes a Views query (whereas 'media_access' is also applied to entity queries that aren't necessarily Views queries) and it doesn't handle relationships.
This patch adds what I think is a good docs example. It replaces the current hook_query_TAG_alter() example, which is an outdated example for node_access, which I don't think we need anymore, since anyone wanting to actually implement node access control should use the grants API.
Comment #48
seanbThe example is really helpful and makes it clear how to do something similar when needed. Even though this is pretty complex stuff. It almost makes me want to do crazy access things :). I like it! 👍
This will also make a big difference for people implementing access hooks!
I don't see anything to complain about. The docs look great. The change itself is small and covered by tests. RTBC!
Comment #49
wim leers🤓 80 cols nit. Fixed. ✅
You didn't mention it in #47, but you added this in #47, and it is adding the generic entity type access documentation improvement that you proposed in #35. 🙂👍
phpcsviolation. Fixed that. ✅EDIT: cross-posted with @seanB. I'm glad we agree this is RTBC-worthy 🤓
Comment #50
wim leersForgot the interdiff 😅 That shows my patch only touches docs. So, no need to test against other DBs again.
Comment #51
gábor hojtsyComment #53
gábor hojtsyYay thanks all! Should this be backported?
Comment #54
gábor hojtsyTagging for release notes as an important fix.
Comment #55
xjmThere's no release note nor any change record here. (Minor-only criticals are queried separately of the tag and listed in their own section if they don't have other disruptions.)
This is a change where an info disclosure was fixed. Sometimes those sorts of security hardenings are disruptive for sites that might have had improperly configured permissions and been relying on the info disclosure. Is there any such disruption here? If so, we should add a CR. A CR would also help us determine how disruptive the change was and whether it's significant enough to be in the release notes as a disruptive change (rather than just a critical fix).
Release note snippet can help explain the importance of the critical issues, but are not a hard requirement really unless there is a disruption from the change.
Comment #56
phenaproximaI don't know if I agree with this assessment.
All this patch did is make Views, by default, add the
{$entity_type_id}_accesstag to its queries. That change, by itself, doesn't fix any info disclosure; it does, however, enable modules to harden their queries if desired.In fact, this patch is not doing anything that custom code couldn't already do. (One could always implement
hook_views_data_alter(), in which the same change could be made.) But it does add test coverage that the query access tag is properly added, and it adds documentation to cover the fact that hook_entity_access() does not run for queries.So this is technically a "behavior change", in that Views will now tag its queries by default, and therefore does, IMHO, deserve a CR. But I suspect it is only a potential disruption for sites which were already implementing hook_query_TAG_alter() for
{$entity_type_id}_accessquery tags. Since that is a possibility, although maybe not a super common case, this deserves a release note too.Comment #57
xjmThanks @phenaproxima!
#31 explains why this is a (minor) information disclosure. But yeah, let's document whatever minor disruptions there may be and write a potential release note based on that.
The release note for this issue can probably be appended to the one for #2969678: Integrate Media Library with Content Moderation.
Comment #58
phenaproximaWrote a release note.
Comment #59
xjmThe release note looks great. I think we still need the full change record for it.
Comment #60
bnjmnmAdded CR https://www.drupal.org/node/3088475
Comment #61
bnjmnm(already committed, switching to Needs Review since the CR should be reviewed)
Comment #62
phenaproximaI think that CR is completely accurate and explains the background, motivation, and potential effects beautifully.
Comment #63
catchThe change record looks fine to me, marking fixed again.
Comment #65
quietone commentedPublish the CR