I found this because of #845838: File/Original image attached to private message does not display when using private files..
There seem to be two different issues...
First, it seems that the following line does not return anything for files/images that are attached to a private message:
$references = file_get_file_references($file, NULL, FIELD_LOAD_REVISION, $field_type);
This might be related to #353458: hook_file_references() was not designed for a highly flexible field storage.
And if that would return something, http://api.drupal.org/api/function/file_file_download/7 only implements custom access checks if the entity this is attached to is either a node or a user. How are other entities (like private messages) supposed to handle this? Maybe some sort of callback so that the entity can tell if the user can view that piece of content?
Or are they supposed to implement hook_file_download themself? then file_file_download() is pretty useless and should be moved to node_file_download() and user_file_download() but that would lead a lot of duplicated code I guess. file.module should imho not have a hard dependency on node.module and user.module even if they are both required.
Comment | File | Size | Author |
---|---|---|---|
#69 | file_get_file_references.patch | 550 bytes | nonzero |
#68 | file.patch | 852 bytes | neoglez |
#66 | file.patch | 838 bytes | neoglez |
#58 | 846296_taxonomy_files.patch | 667 bytes | Island Usurper |
#46 | file_download_access7.patch | 11.98 KB | Berdir |
Comments
Comment #1
moshe weitzman CreditAttribution: moshe weitzman commentedI haven't researched this, but it sounds like field_access('view') should tell you for any entity if the file is viewable.
Comment #2
BerdirHm. In theory, I guess so. Thing is, field_access() just collects data from hook_field_access() and there are no implementations of that in core.
Comment #3
BenK CreditAttribution: BenK commentedJust need to track this issue since I filed the original Private Message bug report....
Comment #4
tstoecklerShouldn't node.module pass on permissions from the node type to the attached fields via a node_field_access()?
And shouldn't user.module do the same, ie grant users with the 'view user profiles' permissions the field-level 'view' permission?
I actually have no clue, but I read the PHPDoc of the related functions, and, going from there, that would IMO make the most sense conceptually.
Comment #5
catchUsing field_access() then having hook_field_access() implementations would get around the hard coding of node and user modules, but it's going to add a fair bit of PHP overhead for every single field - 20 instances on a node, 10 nodes in a list, now you're running node_field_access() 200 times where previously nothing, and that overhead is going to be on sites without private files too.
Comment #6
ridgerunner CreditAttribution: ridgerunner commentedI found what is probably a pretty nasty bug in
file_get_file_references()
where it is improperly callingdrupal_static()
. It is calling it like so...$references = drupal_static(__FUNCTION__, array());
but it is supposed to call it with the
=&
reference operator like so...$references = &drupal_static(__FUNCTION__, array());
I'm not familiar with the file end of the drupal code base here so I have no idea what the ramifications of this fix might be, but this may resolve this issue. Attached is a patch that corrects this error. Let's see if this fix passes automatic testing...
Comment #7
ridgerunner CreditAttribution: ridgerunner commentedNot sure why the patch from the previous post did not get submitted for testing. Trying again with a different filename...
Comment #8
ridgerunner CreditAttribution: ridgerunner commentedOk. Could someone please explain why the above two (identical except for filename) patches were ignored?
Comment #9
ridgerunner CreditAttribution: ridgerunner commentedOk, found the problem. Looks like the status needs to be "Needs Review" to trigger the bot...
Comment #10
BerdirWhile that is a bug, it shouldn't lead to any actual bugs. It simply means that references are not statically cached but re-calculated every time they are being used.
So, while that is a correct fix, it is imho nothing that belongs to this issue.
Comment #11
maxikey CreditAttribution: maxikey commentedComment #12
sunComment #13
BerdirErm, what?
Why has this been won't fixed? In #10, I just said that the proposed patch doesn't have to do with the issue reported here.
Are modules that add fieldable entities supposed to define hook_file_download()? If so, then that needs to be documented and it needs to be done for comments and terms. Also, these modules would have to copy/paste 90% of file_file_download().
Comment #14
ridgerunner CreditAttribution: ridgerunner commentedSorry for the interruption. Carry on.
Comment #15
AaronBaumanHere are (at least some of) the options then:
Comment #16
Damien Tournoud CreditAttribution: Damien Tournoud commentedAs noted in the original post, file and image downloads just don't work currently. See #867928: Files and Images don't work in private filesystem mode.
Comment #17
Damien Tournoud CreditAttribution: Damien Tournoud commentedMaking node and user implement hook_field_access() sounds like the way to go to me.
This said, it will have a performance hit, which proves that field access control is not performed at the correct place. It is currently processed field-by-field into field_default_view(), while it should probably be a multiple hook processed in field_attach_view(). One additional argument to make this change is that field_view_field() currently calls field_default_view(), so that fields displayed by other modules (for example: Views) will *also* be subjected to access control.
Comment #18
catchHaving node_field_access() and user_field_access() introduces that performance hit even for sites not using private files - that's a regression from D6 for all sites which don't use private files (which is still likely to be the majority even with the separation). Making it a multiple hook is a good idea to ameliorate this somewhat but that's a proper API change, whereas hook_file_download_access() wouldn't have any performance penalty, and is only an API addition. Either multiple hook_field_access() if it worked out alright or hook_file_download_access() I can live with though.
Comment #19
BerdirI would vote for hook_file_download_access() too...
Two more questions:
- Should access default to TRUE or FALSE (in case there is no hook implementation for $entity_type)?
- Where should it be documented that this hook needs to be implemented for new entities? It took me a while until I figured out this based on the original bug report....
Comment #20
moshe weitzman CreditAttribution: moshe weitzman commentedSince we have a module_implements cache, I don't really see a significant performance hit for adding a 100-200 (for the sake of argument) no-op function calls. I would prefer that to adding a new hook which is special for files. Not a huge deal either day.
Comment #21
moshe weitzman CreditAttribution: moshe weitzman commentedI guess I prefer whatever patch anyone comes up with to close the critical. Keep existing hook or make a new one.
Comment #22
moshe weitzman CreditAttribution: moshe weitzman commentedRe: #19
1. default to true (allow access)
2. best i can find is http://api.drupal.org/api/function/hook_entity_info/7
At #851878: serve image derivatives from the same url they are generated from, justin notes that image derivitives of private files are not currently access controlled. Might be fixable here.
Comment #23
Gábor HojtsyThis also looks like THE place/function to solve 1/4th of #881578: Solve SA-CORE-2010-002 issues, i.e., the "File download access bypass". Looks like the files list returned could be multiples and the upper/lower case trick also applies. I'll submit a patch over there, just cross-referencing.
Comment #24
BerdirOk, attaching first patch. This is not perfect yet, but works...
file_file_download() currently checks field_access() and hook_file_download_access(). If for all entities, where a file is used, either of those returns FALSE, access is denied.
I had a lot of ideas/questions while writing this:
- Still not sure if hook_file_download_access() is necessary or not. Because of the current implementation, we could also simply change the hook implementations in user.module and node.module to hook_field_access() and remove the hook_file_download_access() and it would work as well. But that would probably require rewriting that hook to support multiple fields for performance reasons. I don't know if I can do that.
- If we keep the hook, we could need a better name...
- If we keep the hook, we need to think about the arguments passed to it (currently $field, $entity_type and $entity)
- Another idea I had to solve this, but this is most probably D8 stuff: Add access permission/callback to hook_entity_info() and with that, allow a generic entity_access($entity). Because all implementations of hook_file_download_access() only check access to $entity, they don't care about $field (that's what hook_field_access is for, after all). But I think that would introduce too many changes. That would also be self-documenting, not like hook_file_download_access(). And that's als why I'm not sure about the name and arguments for hook_file_download_access().
- I haven't even considered writing documentation for hook_file_download_access(), as it might not survive the next patch :)
BTW, I verified that this also works for privatemsg.module, I've implemented and tested the following hook implementation (Only allows access if you have read all permission or are a recipient of the message the file belongs to):
Comment #25
moshe weitzman CreditAttribution: moshe weitzman commentedCode and approach are sound IMO.
- If you have implement multiple in hook_field_access, thats ideal. But I am happy to go with this patch approach as well.
- current hook name is fine
- those args look good to me
- agree, d8 stuff
- thanks for including privatemsg example code. it is helpful when considering the patch.
Comment #26
Berdir- Ill try to have a look at the multiple stuff.
Another question, what about other core entities? Do they need a hook implementation too?
- comments: "user_access('access comments') && $comment->status || user_access('administer comments')"?
- terms: Doesn't look like there's a view permission defined in http://api.drupal.org/api/function/taxonomy_permission/7?
Comment #27
moshe weitzman CreditAttribution: moshe weitzman commentedI do think that comment logic is right and belongs in this hook. As you said, there is no protection currently for term viewing.
Comment #28
BerdirIn the sense of http://cyrve.com/node/23, here is a new patch...
Changes
- Implemented comment_file_download_access() and did some very basic tests
- Tried to improve the comments in file_file_download()
- If file_file_download() fails to load $entity or $field, it denies access (for that reference only). This actually shouldn't happen because a valid reference to that was returned.
I thought about changing hook_field_access() but I'm not even sure how that would work then (should it return an array with access definitions, or should we pass the fields by reference and define #access, ... ) and changing field api to be able to do that is certainly even more complex :) I agree that it would be the perfect solution but I also agree with moshe that it is too late for finding the perfect solution :)
If someone can come up with a working patch, great, but I don't have the time nor knowledge to do that...
And an additional question:
- A file is used in two entities. One does deny access and the second does not define any access checks? Currently, that would mean that access is denied, because $denied has been set to TRUE. What should happen in this case? Allow download? Not sure how to implement that.... I guess we would need to reverse the logic and make it like this "Allow access unless all entities deny access"...
Still todo: hook_file_download_access() documentation
Comment #29
moshe weitzman CreditAttribution: moshe weitzman commentedAbout your 'additional question': I agree with the behavior you have coded.
Code looks ready save for the the doxygen you mentioned.
No tests in this patch. You mentioned those in #28
Comment #30
moshe weitzman CreditAttribution: moshe weitzman commentedPerhaps the comment module hunk needs minor change since #881578: Solve SA-CORE-2010-002 issues was committed.
Comment #31
Berdir- Added documentation for hook_file_download_access().
- Made the status check for comment access a bit more explicit by comparing it to COMMENT_PUBLISHED instead of just a non-zero integer but other than that, the security issue is imho not really related as this already had a status check and the other issue was about edit access...
As you said, no tests yet. We do have a very basic test of a private file on a node, but that's it... Maybe I'll find the time to write some tests for this at the core summit :) What does need to be tested here? A deny/allow test for each entity? Combinations with multiple references would be nice I guess, but I'm not sure how to create that.. And obviously no references but again, I'm not sure how to create that... any pointers?
Comment #32
moshe weitzman CreditAttribution: moshe weitzman commentedI'm satisfied with the existing test on this. We're just down to doxygen tweaks
Not sure what we mean to convey here. Perhaps omit it?
This seems to imply that we are not short circuiting a denial when the code says that we are. Or I am missing the meaning.
Comment #33
BerdirI'm fine with omitting that :)
We only short circuit an allow. For example, if you are a user that can view nodes but not user profiles and the a file is attached to a node and a user profile, you are allowed to download that file even though the reference to the user profile will deny access and it shouldn't matter which reference is checked first.
The following cases allow download:
- At least a single reference explicitly allows access, even if another references denies
- No reference allows nor denies access
That's why I'm not 100% about the default value and the handling when one references denies access and another one does nothing.
Re-roll with the first thing removed.
Comment #34
moshe weitzman CreditAttribution: moshe weitzman commentedOK, that logic makes sense.
Now that I think of it, I think we should add a drupal_alter('file_download_access', $grants) after our first loop. This gives flexibility to site specific modules to get the behavior they want. This is what we added for node access (see http://api.drupal.org/api/function/node_access_acquire_grants/7) so we should do same here. This does mean that we can't short circuit anymore. Not a big loss.
Comment #35
BerdirComment #36
BerdirAh, I think we talked about two different things regarding short-circuit... :)
There are two levels here:
- Whole thing: That's what I talked about. We short-circuit ALLOW in the sense that if an reference allows access, we allow access to the file.
- Per reference: I guess that's what you meant. The old patch did short-circuit ALLOW and DENY for the current reference only. The patch changes this and doesn't, *but* (not sure hot how to explain it), ALLOW is strong than DENY, so TRUE is checked first.
What changed:
As described above, this patch calls all hook_file_download_access() implementations, then calls the alter hook and then checks if there is a TRUE in the array (short-curcuit here!) and last, checks if there is a FALSE (sets denied to FALSE but doesn't short-curcuit the whole thing).
Comment #37
moshe weitzman CreditAttribution: moshe weitzman commentedOK, thats exactly what I meant. RTBC. I'll summarize.
If you add a file or image Field to comments or terms or a custom entity, you currently have no good way to deny to the file if the user has no access to view that entity. Here is example code for privatemsg which uses entities for its messages in D7:
Comment #38
webchickComing to you LIVE from Drupalcon CPH!
Reviewed this patch with Dries, and we both agreed it looks good. But it seems we need expanded test coverage for the original bug? Something like adding a file field to a comment and verifying that access carries over properly.
With that change, looks like this is good to go.
This is also an API change, so tagging accordingly.
Comment #39
webchickComment #40
webchickOh. We also need docs for hook_file_download_access_alter(), too.
I do like the parity here with the node access system. +1.
Comment #41
BerdirTests++ ;)
Added a new test, that does..
- Add a file field to comment on article node type
- remove access comments permission from anon
- create a node and comment with a file
- download file as user with access
- try to download file as anon user without access
Also added documentation for hook_file_download_access_alter()
Comment #42
moshe weitzman CreditAttribution: moshe weitzman commentedgreat. just found one typo: authenticed
Comment #43
agentrickardI found a bunch of problems, discussed them to Joakim, Berdir and webchick. New patch attached. Summary:
-- Added return documentation for hook_file_download_access().
-- Added documentation for hook_file_download_access_alter().
-- Made the $grants array passed to the alter hook contain some meaningful context. As written before, the alter hook was passed a positional array of Boolean values. Not enough context to 'alter'. Now the $grants array is keyed by the module that controls the entity.
-- Default access set to FALSE (assigned to system module), so that at least one entity must assert TRUE for access to be granted.
Berdir's tests are incorporated here.
Comment #44
agentrickardForgot that file.api.php is a new file.
Comment #46
BerdirThere was a dsm() left over in the previous patch, removed that.. let's see if this passes..
Comment #48
moshe weitzman CreditAttribution: moshe weitzman commentedOK, I think we are ready.
@agentrickard - nice catch in #43 with extra context in hook and change to $grants deny logic.
Comment #49
webchickGreat work, folks! Committed to HEAD.
Randy, the summary of this API change is in #57 (but cross-reference with the example docs in file.api.php because I think the function signature changed in the latest patch).
Comment #50
agentrickardOh man, the dsm() made poor Andrew re-test the patch all morning. D'oh!
Comment #51
rfayOK, looking for the issue and API change summary. Not finding it in #57 :-) Can somebody point me to it or write it?
Thanks!
Comment #52
agentrickard#37 and #43 have the revelant features. There is also a new file.api.inc that explains the new hooks,
Comment #53
BerdirI think we have some inconsistencies between the comments and the actual code due to the changes added by agentrickard (which are fine, just the comments aren't correct anymore). Also, it looks like the code could be simplified a bit.
This is the problematic part:
Because of the system => FALSE change, that is not true anymore. Instead, it will deny access if no module allows access and there are references. *Except* someone actually implements an alter hook to remove the system => FALSE default for whatever reason. That special case is also the only case when the default value would be used because #898036: Private images broken (@agentrickard: remember that the private image field didn't work when we tested the patch? That issue fixes that... ) is going to add an empty($references) check and returns early.
Powered by Dreditor.
Comment #54
sunLooks like there's no longer an API change here, just needs a documentation fix.
Comment #55
jhodgdonCan someone clarify what documentation needs to be fixed? Webchick mentioned a summary above, but she said it was on comment #57, which doesn't exist (and neither does #47).
And does this change need to be put into the module 6/7 upgrade guide? If so, please tag "Needs Update Documentation". Thanks.
Comment #56
Island Usurper CreditAttribution: Island Usurper commentedI think we still need a taxonomy_file_download_access() for files attached to terms and vocabularies. Try adding an image field from the private filesystem to a term. If there are other field data visible, you should see a broken image. Since there's an alter hook already, just returning TRUE when the $entity_type is 'taxonomy_term' should be enough for core.
Comment #57
rfayLooks like an x-post cleared the Needs Documentation tag.
Comment #58
Island Usurper CreditAttribution: Island Usurper commented3 weeks is a long time for a cross-post. Sorry, but I meant to take it off because I wanted to indicate that we need some more code. I guess what we really need are more tests.
Comment #59
johnvI encountered the error as described in #56 and posted this issue: Private files broken for Filefield/Imagefield in Taxonomy Term.
Then I found this issue and I applied the patch in #58, but with no results. Do I need to apply more patches?
Comment #60
BerdirHave you cleared the cache after applying the patch?
Comment #61
johnvThat has become a second nature of me, but in this case, I didn't. :-( Thanks for the tip!
Works, like a charm, and I hope it gets in a next -dev version! --> RTBC
Comment #62
Akaoni CreditAttribution: Akaoni commentedThere seems to be an issue with this in the Drupal 7 release code.
/modules/file/file.module Line 143
The functionality spelt out in the commments is sound.
Problem is, if a field of $field_type is defined but doesn't contain a reference to the file what's returned isn't empty().
Example return:
This means that files where there is no reference node (or the reference node isn't accessible to the user) are always accessible.
I haven't got GIT setup yet otherwise I'd submit a patch. :(
Comment #63
jhodgdonRight. This issue has not been marked "fixed", because it hasn't been fixed. Please leave the version as 7.x-dev, since it hasn't been fixed in the development version, much less the released code.
And there is already a patch in #58, and in fact it has already been reviewed. Please don't change the status.
Comment #64
jhodgdon#58: 846296_taxonomy_files.patch queued for re-testing.
Comment #65
Berdir@jhodgon The existing patch addresses a different problem (And should probably be in a different issue, since it's really about something additional based on the new API added in this issue), #62 is correct (as in: the (my :() code is wrong and needs to be fixed), so moving this back to needs work.
@Akaoni: would be great if you can create a patch, git isn't that hard to install :) And you can also create a patch with any diff utility, you just need to make sure that you are diffing from the top-level directory and changed file has the correct name ("modules/file/file.module") so that the test bot can apply the patch.
Comment #66
neoglez CreditAttribution: neoglez commentedHi!
I've been fighting with this for a couple of days already. I must say that i consider this a very (security?) seriousone. I did extenses tests and i came up with the following clonclusion:
As long as the file access is managed by Drupal core functionalies (no contributed AC module acting over node_access table) the behavior is as expected but in the presence of such a AC module the query that is constructed in file_get_file_references:
returns populated arrays (by reference, the query is executed as many times as fild instances of the type 'file' are) of only nodes the user can access, wich IMO shouldn't be becouse then we are NOT going to have the posibility of calling the hook_file_download_access or _alter for files attached to that node and according with the documentation that is exactly what this hook is for:
On the other hand it seems that for other types of entities than nodes, it should work fine, the following is an example of the query executed by file_get_file_references on a node with a file attached and in the presence of the Tac Lite (AC module) (note the field_data_field_datei0.entity_type <> 'node'):
Here we see that the query checks (when the entity is 'node') the access on the node to return the result, delivering an empty array and therefore bannig file_file_download to call the corresponding hooks.
I'm posting a patch to resolve the issue when the $references array is an array of empty values but i really think references should always be returned and not only when the entities they are attatched to are accessible by the user.
The patch is against the 7.0 -> changing Version and Status to schedule tha patch test.
I consider this major becouse it limits the posibility to use contributed AC modules, exposing the private files attached to a nodes, even when nodes are not accessible.
Comment #68
neoglez CreditAttribution: neoglez commented...Ups, i'm sorry, it's the first time i'm posting a patch..
Comment #69
nonzero CreditAttribution: nonzero commentedHm, this seems similar to what I've been doing. I haven't compared your patch with mine in detail yet though. Just throwing this out there for more eyes to see.
The problem is that file.module :: file_get_file_references() sets $references[$field_name] to an empty array if the EntityFieldQuery fails to find the file. EntityFieldQuery returns an empty array because it actually does honor node_access and finds the file restricted. Unfortunately, setting $references (even to an empty array) leads the calling function, file_file_download(), to try to traverse the empty array looking for access denial. Since it's empty, the function assumes access is granted. Attached is my patch to fix the problem by checking if the EntityFieldQuery returned an empty array.
Comment #70
nonzero CreditAttribution: nonzero commentedIt appears that my patch tackles the bug more upstream, which is good if code elsewhere uses file_get_file_references(), whereas #68 only works within file_file_download().
Comment #71
neoglez CreditAttribution: neoglez commented@nonzero I DO really think that would be a nice code to patch file.module (it adresses a more general issue) but the question is:
Should file_get_file_references() honor node_access or other access call on the entities, files are attached to ??
V.S.
Should implement file access checks regardless of node_access or other entities' access check??
As long as i understand the answer is affirmative to the second question (see the blockquote i posted) and in that case neither your nor my patch adresses the issue, the query is just wrong constructed.
I repeat:
references should always be returned and not only when the entities they are attatched to are accessible by the user.
It's just a matter of Business Logic.
This issue is assigned to Berdir, opinions from him (and the community of course) are wellcome.
Comment #72
BerdirI'm assigned because I worked on this during drupalcon cph, removing... :)
Yes, that is the question that needs to be answered, I'm not sure.
Comment #73
agentrickard@neoglez
I disagree, simply because file_get_file_references() is merely a helper function for file_file_download() and is designed to force an access check. No other core functions call this code.
I think you want a new function or query to return all files attached to an entity, which is valid but not required in this context.
Otherwise, the solution would have to be an API change that passes file information and access information back to the caller.
Marking #69 RTBC.
Comment #74
rfayD8 first. Let's get these fixed on D8 and into D7.
Comment #75
catchThis is a duplicate of [#1168756] afaik.
Comment #76
David_Rothstein CreditAttribution: David_Rothstein commentedComment #77
David_Rothstein CreditAttribution: David_Rothstein commentedThis issue was unpublished a few years ago, because the followups discussed here touched on a security issue. However, that security issue was fixed a long time ago in SA-CORE-2011-001. I'm republishing it now because I want to refer to it in #2305017: Regression: Files or images attached to certain core and non-core entities are lost when the entity is edited and saved, which relates to some of the code that was originally added here.
I'm also moving this issue to "Fixed" because the original patch here was committed to Drupal 7 a long time ago. Besides the security issue, the two other followups I see are: