My receipe for mixed private/public file system
Just for sharing my receipe about a mixed solution with public and private files.
I'm not a Drupal guru ... and still in my learning curve of Drupal ;-) So if you see a "security hole" or a better way (simple way ;-) to achieve the same goal, let me know !
Goal:
I wanted a simple solution to control the access by role to some folder. In my example: a folder specifique to my cck content type
The problem:
- if I set all the file system for private, some fonctionalty don't work (color chooser of garland, performance css, etc ...), and I will overload my server
- only a small part of the files uploaded to my site need to be private (all the css, img etc can stay public)
- I want to control the file access by the internal Drupal Role system.
Pre-requies:
- apache mod_rewrite (if clean url work for you, you're ok)
- cck, cck filefield and a content type of your own
Solution:
For restrict the acces to a folder we will redirect automaticaly all request to /files/private/xxx to /system/files/xxxx and handle the security in Drupal:
Solution step by step:
- set the file system to public. Files are accessible by /files/xxxxxx. But they are also accessible by /system/files/xxxxxx (and this this this trick that we will use ...)
- create a folders in /files/. For example: /files/private/MY_CONTENT_TYPE_FOLDER/
- create the file in the folder /files/private/.htaccess to do the automatic redirect to /system
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /system/files/private
RewriteRule ^(.*)$ $1 [L,R=301]
</IfModule>Rem: if your install Drupal in a virtual directory for example www.xxx.com/mydir/ , the RewriteBase should be /mydir/system/files/private instead of /system/files/private
- Then, every request to a file in /files/private will be redirect to /system/files/private/. All othe folders are not affected by this setting.
- Create a module, activate it etc .... (perhaps there is a better solution here ... ) containing:
function MYMODULE_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'system/files/private/MY_CONTENT_TYPE_FOLDER',
'access' => user_access('access MY_CONTENT_TYPE_FOLDER'),
'type' => MENU_CALLBACK,
);
return $items;
}- then in the /admin/user/access check the "access MY_CONTENT_TYPE_FOLDER" for the role you want to grant access
- In my CCK content type, I add a filefield and set the folder to private/MY_CONTENT_TYPE_FOLDER. The
Perhaps there is a potential for integrating this principle in the filefield ?
Regards,
Nico.

Thanks
I'm going to try this out when I get a chance. Have been looking for a solution for a while. Will let you know how it goes. I am running multi-sites so I'll see if I can add to this solution.
--
Prometheus
Looking desperately for a solution...
I haven been looking desperately for a solution for this scenario, as far as I'm building community and group sites with public and private file areas.
My "solution" was a combination of modules:
* webFM for private "intranet" documents...
* and drupal's upload core module for public ones (or "viceversa").
But I'm not proud of this because:
a) You need "private downloads" to secure a little this mess... and "private downloads" will kill your server's performance (http://drupal.org/node/67366), as well as collides with CSS optimizations, "colors" (garland), multisites, video and a quite long list of contrib modules.
b) It's really bizarre for my users to understand that they need to follow different interfaces and upload processes depending on document's privacy.
Last summer a SoC project aims to develop a Document Management Module... but I didn't see much results.
This new SoC project looks also promising, but looks like we need to wait until D7: http://drupal.org/node/166759
Here you can find some people asking for a similar scenario: http://drupal.org/node/126055
Some try to fix it by their own: http://drupal.org/node/116843
Here you can follow an interesting discussions about "public&private files":
http://drupal.org/node/102584
http://drupal.org/node/128876
...
I love to see someday something like Joomla-DocMan running over a Drupal but I don't have the knowledge to develop a huge tool as this... so my only choice is to wait and cross my fingers, sending luck and best wishes to the brave developers that I suppose are working to improve drupal "private & public downloads".
BTW, a "Knowledge Tree" integration module was developed some time ago... but I didn't find the moment to test.
Looks interesting, I may
Looks interesting, I may give this a go. However, if the file is being read through php what effect does that have on php's memory requirement? Or is php clever enough to stream the file? Memory is often a limiting factor especially on shared hosting. Pictures are probably ok but video might be a problem.
Tried this but the code seems to be 4.7 not 5 so I think I fixed that. But then I tried a test page with this
<img src="/files/images/group.jpg">in private
<img src="/system/files/private/images/group.jpg">
The is first image showed, the second didn't. I had no htaccess at this time so if the according to the post both should have pointed to the same place. Files access is set to public. Either I have something wrong or system access isn't working, perhaps because access is set to public? Or perhaps I've misunderstood something.
Hi Malcolm, this solution
Hi Malcolm,
this solution allow you to set file access to public (for some feature of drupal: Color Picker, aggregate CSS etc ...), and at the same time control the access to some files like the private mode.
You need a .htaccess file with the directive, you need the mod_rewrite activate on your share hosting.
The idea is that any file put in /files is accessible by 2 url:
- directly:
- or control by drupal:
As you specify in your post it could be a bootleneck if it's a big file send via /system because php will "control" the send (and not apache alone)
In your exemple, group.jpg should be on only 1 folder:
- if you want a public acces, put your file in /files/images/group.jpg
- if you want to control access, put your file in /files/private/images/group.jpg
Thanks for the reply.
Thanks for the reply.
I can't seem to get system/files to work at all. Leaving aside mod_rewrite (I have deleted .htaccess for now), I have created a page with an img tag where the src = "/files/images/group.jpg" and another where src="/system/files/images/group.jpg". File access is set to public. The first shows the image the second doesn't and this is when logged in as admin. Why doesn't this work? I have none of the code in this idea in at all.
What is the result when you
What is the result when you try directly in the url of your browser:
www.yourdomainxxxxx.com/system/files/images/group.jpg ?
1 - a clean page "Access denied of drupal"
2 - blank page or other error ....
In the 1 case,:
you don't have the right to access this url. Ti access /system/xxx you should have on of the perm:
- administer site configuration
- access administration pages
- select different theme
But if you say you are admin (user with id=1 full right), this sould not be your case.
In the case 2, perhaps a problem with you host, for you info the code involve when a file is acces by /system/file is (drupal 5)
- file_downlaod line 582 in /includes/file.inc which call file_transfert
Hope that could help you.
Regards,
The result is page not
The result is page not found.
Digging some I find that system/files calls function file_download() not function file_transfer($source, $headers). I am running 5.6 version BTW. In that $filepath gets set to images/group.jpg so that seems right. Next file_create_path($filepath) returns files/images/group.jpg so that's right. The next bit:
if (file_exists(file_create_path($filepath))) {$headers = module_invoke_all('file_download', $filepath);
print_r($headers);
if (in_array(-1, $headers)) {
return drupal_access_denied();
}
if (count($headers)) {
file_transfer($filepath, $headers);
}
}
I have stuck in a print_r($headers); which prints Array() which I think means blank array so I don't understand what module_invoke_all is doing. But as $headers is empty then count($headers) is false so file_transfer doesn't fire.
The comments on file_download() suggest that something should respond to say the file is accessible, otherwise it does nothing, which is what it is doing.
I can't follow. is this a
I can't follow. is this a solution or is this a circle walk?
Summary
I was trying the suggestion by nico059. It didn't work for me and I trying to establish what I'm doing wrong or what is different in nico059 drupal. Nico059 says that, for example, /private/files/restricted/myfile.jpg always aliases to /files/restricted/myfile.jpg. But that doesn't happen for me (unless I guess use private files is ticked which defeats the object). And that is where it stands as now.
need help for /system/files/xyz
Hi malc_b,
sorry to not be able to help you. To go back to the root of your problem (and forget temporarly my receipe), you seem to have a problem accessing a file with the /system/files/xyz url ... Perhaps try to solve this problem, and then try my receipe for a mixed of private / public file access.
Works Great!
I was looking for this exact solution. I tried Private Upload module but it only works with the Upload module. I needed it to work for CCK imagefield and was thinking I would have to do something like this so Thanks!
A couple of things:
1. There is a syntax error in the code you posted. There needs to be a closing brace } right before the line containing return $items; in order to close the if statement.
2. You should probably add
SetHandler This_is_a_Drupal_security_line_do_not_removeto the first line of your .htaccess file for security reasons.Could someone kindly help me
Could someone kindly help me to use this method?
Ok. I have the apache mod_rewrite on. Check. I created a directory /files/private/privatetest/ and put the .htaccess there.
Then I put module called 'privatemodule' inside sites/all/modules
The 'privatemodule' contains privatemodule.module and privatemodule.info. The .module contains this snippet:
<?phpfunction privatemodule_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'system/files/private/privatetest',
'access' => user_access('access privatetest'),
'type' => MENU_CALLBACK,
);
}
return $items;
}
Ok. Now as you can see I fixed the syntax error code in original nico059's post just as amaria adviced (also added the SetHandler This_is_a_Drupal_security_line_do_not_remove line to the .htaccess)
I can activate the module fine but when I go to the /admin/user/access to find the check the "access privatetest" for the role I want to grant access, it simply is not there! Did I miss something on the way?