I love WordPress. I like Gravity Forms. The only trouble with Gravity forms is if you add a file upload field to your form it is stored within your /wp-content/uploads directory and as such is publicly accessible.
Now the link that Gravity Forms generates is fairly long (mywebsite.com/wp-content/uploads/gravity_forms/reallylongstringofnumbers10239812039802193/year/month/filename) however that just isn’t secure enough for me. The worst part os they don’t seem to care.
After a week or two of searching I have found a way of securing it! The following method secures the folder in question so that only Logged in users can access them. It is also probably possible to modify this method to account for different user roles etc, but for now a little security is better than none!
Ok so we need to do 2 things.
- Tell our website that all files in a certain directory need to be passed to a special file for processing before downloading.
- Use our special file to check if the user is logged in!
<?php require_once('wp-load.php'); is_user_logged_in() || auth_redirect(); $upload_dir = wp_upload_dir(); //Set your path below I am using /gravity_forms/ $basedir = $upload_dir[ 'basedir' ] . '/gravity_forms/'; $file = rtrim( $basedir, '/' ) . '/' . str_replace( '..', '', isset( $_GET[ 'file' ] ) ? $_GET[ 'file' ] : '' ); if ( ! $basedir || ! is_file( $file ) ) { status_header( 404 ); die( '404 — File not found.' ); } $mime = wp_check_filetype( $file ); if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) ) $mime[ 'type' ] = mime_content_type( $file ); if( $mime[ 'type' ] ) $mimetype = $mime[ 'type' ]; else $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 ); header( 'Content-Type: ' . $mimetype ); // always send this if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) ) header( 'Content-Length: ' . filesize( $file ) ); $last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) ); $etag = '"' . md5( $last_modified ) . '"'; header( "Last-Modified: $last_modified GMT" ); header( 'ETag: ' . $etag ); header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' ); // Support for Conditional GET $client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false; if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) $_SERVER['HTTP_IF_MODIFIED_SINCE'] = false; $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); // If string is empty, return 0. If not, attempt to parse into a timestamp $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0; // Make a timestamp for our most recent modification... $modified_timestamp = strtotime($last_modified); if ( ( $client_last_modified && $client_etag ) ? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) ) : ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) ) ) { status_header( 304 ); exit; } // If we made it this far, just serve the file readfile( $file );
Looks kinda scary doesn’t it! Don’t worry about it! The only thing you need to pay attention to is line 8. This is where you set which folder you are protecting, for this I am using /gravity_forms/ the path is relative to your uploads directory.
Second in your .htaccess file you need to add the following:
#Gravity Forms Upload Protection RewriteCond %{REQUEST_FILENAME} -s RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ dl-file.php?file=$1 [QSA,L]
The RewriteRule path needs to match the path you set in your dl-file.php so here you can see gravity_forms is mentioned again.
So what happens.
When a file is requested from within the Gravity Forms folder the request is passed via the .htaccess file to our dl-file.php. This file then checks to see if the user is logged in. If not logged in they are forwarded to the login page, after logging in the file is downloaded. If they don’t login. No file. Simple!
There are probably loads of improvements that can be made to this but it is a great start.
The credit goes entirely to http://wordpress.stackexchange.com/a/37743/12438 I simply took the code and made it work for me! Let me know what improvements you make to it!
Hey,
Thanks for the code.
But, what about protecting more than one directory?
Thank you!
2021 and this is still a viable solution! It works in Chrome and Firefox, however Safari (on Mac and iPads) reports ‘cannot parse response”…
Will your solution work with gf-download?
eg. index.php?gf-download=2017%2F11%2Ftest-upload.pdf&form-id=47&field-id=2&hash=b33b1995b8d3a6fbc23eec70401c9362c6990ffa0b50dd9c951821d23a537a6c
It doesn’t for me…
this is brilliant. It is prompting me to login when I click on the upload icon or view .pdf. if i go to the name of the individual then view, it passes through the attachment with out the prompt.
It will not accept my password
disregard.
Trevor Gehman 2 years ago
Great solution to a terrible oversight by Gravity Forms. I changed the authentication portion to this, so that it just throws a 404 error instead of redirecting to the login page:
if(!is_user_logged_in()) {
status_header( 404 );
nocache_headers();
include( get_query_template( ‘404’ ) );
die();
}
fixed it. works great!
Hi, thanks a lot for this solution !
I just had to activate the rewrite_mod in my httpd.conf and everything worked great 🙂
I think I am the only one with this problem but this is not working for me. I have placed .dl-file.php into the root of the WP install, modified the root’s .htaccess as directed, but am still able to see the files just fine. I also tried modifying the path on line 6 to /gravity_forms/ (to include /wp-content/uploads) but that also did not help.
Does this secure all subdirectories as well? Because as you know Gravity Forms places form submissions into its own sub-directory by date, like 3-227c1900b53af23b0bf2e3f9df5a0b40/2015/12
Suggestions?
Update: Here is what DOES work.. Replacing the lines in .htaccess to this worked:
#disallow access to file uploads unless user is logged in
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in_.*$
RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ http://path-to-your-error-page.com/ [NC,R,L]
Hi, great solution, thanks. I am just not clear on which .htaccess file needs to be modified with the 3 lines of code. The .htaccess file in the root folder of the WordPress installation or the .htaccess file that is located in the upload/gravity-forms folder?
Thanks.
It’s the one in your WordPress root you want to add it to.
Thanks, works great! 3.5 years later and still relevant 🙂
Hi, thanks for that nice script. But its not working on multisite installation right? Do you have a solution how to protect the folder on multisites?
Sorry I’ve not tested with Multisite at all.
It would be a minor improvement to pass a message to the login page that reads somethng like “You must be logged in to view this file”. Otherwise – great solution, thanks a lot.
It is a big oversight by Gravity Forms. I wish I found your solution a while back.
When I secured the directory (using simple htaccess passwords, so less elegant) I also wanted to move the directory location. I published my code here -> http://badlywired.com/technical-stuff/2014/10/17/moving-where-gravity-forms-stores-uploads/ hope it helps some one that needs it.
Still usefull after almost 3 years! Thanks for this Gravity-forms solution.
Thanks I’m glad this is still useful to people! I’m surprised that after all this time it still hasn’t made it’s way into the Gravity Forms core!
Great solution to a terrible oversight by Gravity Forms. I changed the authentication portion to this, so that it just throws a 404 error instead of redirecting to the login page:
if(!is_user_logged_in()) {
status_header( 404 );
nocache_headers();
include( get_query_template( '404' ) );
die();
}
Excellent & elegant solution to a real problem. Works like a charm. Thank you so much!
This is great. I would rather it redirected to the login page (with wp_login_url() set to redirect to the file). I’m still trying to get into the code, let me know if you have a fix for that handy.
Hi, Is there a way to protect multiple folders?
Hi – just wanted to say that I got this working now and it’s wonderful! I even used a modified version of your PHP file to password protect some additional directories unrelated to gravity forms. This was hugely useful and I am very grateful.
Thanks! Working as expected. If you haven’t already sent this on to the Gravity Forms developers, please do. There’s no reason this shouldn’t already be included in the setup options or among their documentation.
I just tried this and it did not work. I was still able to access uploaded documents even when not logged in. No idea why. Is there any specific place within .htaccess where the 3 lines of text should go?
No but you do need to ensure that mod_rewrite is enabled and that the path to your uploads is set correctly in both files.
Not sure I understand… the path in line 6 of dl-file.php? You have it as ‘/gravity_forms/’
Do I need the full path, ralative to the wordpress root?
Sorry, I meant relative, not ralative. 🙂
The path is relative to your WordPress uploads folder so in my example wp-content/uploads/gravity_forms.
You only need to change it if you are using a directory named something different to gravity_forms.
I appreciate the help, thanks. It still did not work for me, but I have a feeling I have some deeper issues that need to be fixed. If your method works for 90% of users, you have a great idea here!
This is a great post and has been very useful to me. Thanks. One thing worth pointing out though is that if you use the Really Simple Captcha integration with Gravity Forms then it stores the generated png in the gravity_forms directory, as this script stands it will not be displayed on the front end.
I modified it slightly so there is a check up front for the filetype to see if it’s a .png and then just serving the file if it is. ie;
Hope this helps, cheers.
Thanks for your comment, I’ve also fixed your typo as you mentioned in your other comment.
Thanks for putting this together! Saved me lots of time searching the web! Noticed that the copy on github is giving an error because it is missing:
require_once('wp-load.php');
I also modified it to only allow access to the gravity_forms folder if the user is an admin by replacing:
is_user_logged_in() || auth_redirect();
to include the user role:
( (is_user_logged_in() ) && (current_user_can('administrator')) ) || auth_redirect();
Thanks for letting me know I’ve updated the Gist.
I have uploaded the dl-file.php to the root of the wp installation, and made the following alterations (see below) to my .htaccess file – but when I navigate to the uploaded file in the gravity forms directory I don’t get redirected. Instead, my browsers just show the text from the the dl-file.php in a paragraph. I have messed around with this for hours, please tell me I’m overlooking something really simple here? Thanks, Owain
Nothing I can see there although I did notice that in my code example there was a closing peer tag at the start for some reason (probably from when I changed syntax highlighters). Do you have that in your code if so remove it. And make sure you have a opening php tag at the start of the file as well!
Please excuse how silly this is going to sound, but what does a closing peer tag look like? Do you mean ‘?>’?
In the line below, is the closing tag the ‘;’ ?
Thanks for your help!
Sorry for the confusion I made my reply from my iPad which auto-corrected everything I said.
Ok there were 2 issues with my code example.
1) On line 1 there was a
tag which shouldn’t have been there.
2) I didn’t including the opening
tags as I hoped everyone would insert that themselves.
I’ve fixed the code example above and have also put the code on GitHub so you can simply get the whole thing. Let me know if you get this working!
GitHub Gist
Works great for me! Do you think this could made into a Gravity Forms add-on?
Possibly but as it needs to write to the .htaccess file to make it work I’m not sure how easy it is do do this from a plugin.
Hey man,
I’ve used Super Cache in the past and it def. does write to the .htaccess file. I’m way more of a theme author than plugin developer, so I have no idea how it is actually accomplished. But I have seen it happen.
Also, the Retina @2x plugin writes to the .htaccess file as well.
And of course, thanks for the code!
Brilliant solution, I’m trying it out at this very moment. Thanks for sharing it!
Thanks, I will give this a try.