vertigo Veteran 135 Posts user info edit post |
I am using sessions to require that a person be logged in in order for them to view pages within a secure directory. It's pretty standard stuff, I think. What I am not sure about is how to require a user to be logged in so that they can download a file. For example:
- link to document A is on both page 1 (outside of the secure area) and page 2 (inside the secure area) - document A is stored within the secure area - page 2 is within the secure area, so the user has to be logged in to see it anyway - page 1, however, is outside the secure area - how do make sure they can't download document A without logging in first, but still be able to see the link on page 1 (outside the secure area)?
Thanks!
[Edited on June 1, 2009 at 3:11 PM. Reason : I'm sorry, this is in PHP.] 6/1/2009 3:11:04 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
.htaccess ? 6/1/2009 3:19:54 PM |
evan All American 27701 Posts user info edit post |
link to a new php script that checks for the session data/valid login
if it's valid, either:
a) redirect with a 302 to the actual location of the file or, preferably b) present headers to the browser saying you're sending it a file with the appropriate mime type (application/octet-stream, image/png, etc)
something like:
<? session_start();
$file = '/the/absolute/local/path/to/your/file.exe'; // obviously make sure you sanitize this or turn off register_globals
if ($yourSessionValidationFunctionBool == TRUE) { header("Content-type: application/octet-stream exe"); header("Content-Length: " . filesize($file)); header('Content-Disposition: attachment; filename="file.exe"'); readfile($file); } else { header("HTTP/1.0 404 Not Found"); include('/path/to/a/custom/404/page.php'); // if you want }
?>] 6/1/2009 3:27:11 PM |
vertigo Veteran 135 Posts user info edit post |
darkone - I thought it might have to do with .htaccess, but I wasn't sure how to incorporate the PHP session variables into a check system within the .htaccess files. I mean, obviously I knew that a root-level .htaccess file would ensure that the user must log in if they wanted to go beyond that point to any sub-directories, but I'm not sure how to implement that.
evan - I don't think I understand. Are you saying that page 1 (outside of the secure area) links should look like this:
<a href="/secure/file.php?f=documenta.pdf">document A</a> and that file.php should be the check?
If that's the case, then couldn't someone manually enter http://www.mysite.com/secure/documenta.pdf and download it without needing to log in? I thought that's where .htaccess would come in - if there was one inside the secure folder, it could always check for login credentials before allowing passage throughout the directory and sub-directories. I'm just not sure how to implement that.6/1/2009 3:38:22 PM |
evan All American 27701 Posts user info edit post |
basically, yes.
the key is you shouldn't put the file in question in a web-accessible directory
i.e. if your webroot is /home/vertigo/public_html/ stick the file in /home/vertigo/files/ or something
no one can get to anything below public_html, so no one at all could access your file unless they use the php script. even more secure than htaccess. also, this way you don't have to bug your users with an htaccess prompt - you can just use the existing session authentication.
there are ways to get php to use htaccess credentials, but this is much more elegant, imo.
also, don't use the filename as a get var - you're just asking for trouble. use some sort of unique identifier and then do a switch case in your code to map the IDs to the correct file paths.
i.e.
download.php?f=supersecretfile
and then, in download php
<?
session_start();
switch ($_GET['f']) { case "supersecretfile": $theFile = "/your/path/to/the/file.pdf"; $theFileName = "a_new_name.pdf"; // filename you want to tell the browser to save the file as $theMIMEType = "application/pdf pdf"; case "anothersecretfile": $theFile = "/your/path/to/the/other/file.exe"; $theFileName = "poot.exe"; // filename you want to tell the browser to save the file as $theMIMEType = "application/octet-stream exe"; default: die("no file parameter given"); }
if (yourLoginValidationFunction() == TRUE) { header("Content-type: " . $theMIMEType); header("Content-Length: " . filesize($theFile)); header('Content-Disposition: attachment; filename="' . $theFileName . '"'); readfile($theFile); } else { header("HTTP/1.0 404 Not Found"); include('/path/to/a/custom/404/page.php'); //if you want }
?>
you could even get elegant and make it so certain users only had access to the file for a certain amount of time, etc.
[Edited on June 1, 2009 at 4:54 PM. Reason : i'm feeling nice today]6/1/2009 4:41:42 PM |
qntmfred retired 40726 Posts user info edit post |
and use a unique non-predictable file id. don't just use 1, 2, 3, 4, 5, etc or else people will just change the fId in the URL and download any file they want 6/1/2009 4:56:01 PM |
evan All American 27701 Posts user info edit post |
yeah
if i were doing this, i'd use something like the MD5 hash of the file. 6/1/2009 4:57:54 PM |
quagmire02 All American 44225 Posts user info edit post |
for all the faults of ncsu's WRAP, it was useful for things like this
essentially, the .htaccess file contained all of the unity IDs that could access the directory...i wrote simple coldfusion and php management tools for authorized users to change who had access to which directory, without them needing to know anything about .htaccess in particular...in any case, it was a quick and easy way to restrict access without needing to write an authentication app
that doesn't really help the OP 6/2/2009 7:36:47 AM |
jbtilley All American 12797 Posts user info edit post |
/public_directory/page 1 /public_directory/secure_directory/document A /public_directory/secure_directory/page 2
When they click on the link to document A (on page 1 or on page 2) it will attempt to access the file in the secure directory (that has a .htaccess file). If you aren't already authenticated it will prompt for log in. If you are already authenticated (like clicking the link when on page 2) it will just go ahead and allow you access.
Is this using WRAP? If so .htaccess would look like the following:
AuthType WRAP require user <unityid1> <unityid2>
Where <unityid1> and <unityid2> are replaced by the unity IDs of the people you want to grant access to. They are separated by spaces and don't include the '<' or '>' characters.
or
AuthType WRAP require known-user
To allow access to anyone that can authenticate with WRAP. There are other examples of allowing access to certain groups or blocking access for certain individuals, these are just a few examples of common usage.
I believe everyone's unity space supports WRAP/.htaccess by default. http://www4.ncsu.edu/~unityid/ If you are running your own server you'd have to install and configure WRAP to work with your server - with the university's permission of course.
[Edited on June 2, 2009 at 8:32 AM. Reason : -]6/2/2009 8:23:20 AM |
evan All American 27701 Posts user info edit post |
yeah, i'm gonna guess that this project has nothing to do with the university whatsoever
also, NCSU won't let you use WRAP on servers that aren't department-owned, if i recall correctly. 6/2/2009 9:45:01 AM |
BigMan157 no u 103354 Posts user info edit post |
http://httpd.apache.org/docs/2.0/programs/htpasswd.html 6/2/2009 11:04:23 AM |
vertigo Veteran 135 Posts user info edit post |
quagmire02 - Thanks anyway.
jbtilley - Thank you anyway, but I don't work for NCSU so the WRAP system doesn't really do me any good.
evan - It doesn't.
The problem is that I don't have access to any directory outside public_html, although I might be able to request it. Is there any way to disallow direct file requests? Maybe something in .htaccess that will kick out any requests that don't come from file.php?f=filename (instead of just filename.pdf)?
[Edited on June 2, 2009 at 11:08 AM. Reason : BigMan157 - I didn't see your post before I replied. I'll check it out.] 6/2/2009 11:06:56 AM |
quagmire02 All American 44225 Posts user info edit post |
^^ i'm pretty far from an expert and i've never used htpasswd, but i don't think that will do him any good (or maybe it will and i'm just making assumptions without enough information)...i assume that he's got users and passwords stored in a database (or does that not matter?) 6/2/2009 11:16:27 AM |
evan All American 27701 Posts user info edit post |
^^if you stick an htaccess file blocking access to that entire directory all together, you should be good. PHP doesn't care about htaccess, all it contains is apache config directives. that readfile() function uses the filesystem directly. so, as long as the user apache runs as has at least u+r on the file, you should be good.
just make a separate directory for your secure files that you'll present using your php script, and then put an htaccess file like this in there:
<limit GET POST PUT> order allow,deny deny from all </limit>
you should get a 403 response when you try to access the files via the web, but the php script i wrote should still work.] 6/2/2009 7:37:04 PM |
vertigo Veteran 135 Posts user info edit post |
I'm not quite sure how to do this. The files are all stored in a MySQL database with a unique ID, their title, and the file name. The more I think about it, the idea of using something like download.php to get the file is useful. All of the files are stored in their own directory, so it's not a bad idea to use the method that evan posted about the .htaccess file. However, evan, the problem is that the PHP script you provided earlier looks to be too manual. I was just using a query and a while loop to dynamically generate a list of links (where the link points to the file and the link text is the title of the document) and there are far too many files to write them out individually. Also, the number and types of files will change on a pretty regular basis.
Currently, my download.php script gets the file from URL and then uses header() to redirect. Using the .htaccess that evan posted, however, won't let it download (I assume because it's using header()) and it gives me a "Forbidden" message. If I use readfile() instead, it just gives me a blank page without errors or a file download. Removing the .htaccess lets me download via header(), but I still get the blank page with readfile(). Any other suggestions as to how I might keep the .htaccess configuration that disallows all access except through a PHP script?
Thanks! 6/12/2009 10:23:21 AM |
vertigo Veteran 135 Posts user info edit post |
Nevermind, I think I got it. I wasn't using the header()s to identify the content, so it was dying on me. I think what I need to do is have it check the extension of the requested file and fill in the mime type dynamically. 6/12/2009 11:31:55 AM |
evan All American 27701 Posts user info edit post |
^^yes, you have to actually have php send the file stream to the browser, as in have it read from the filesystem. and yes, you need to present the proper mimetype to the browser - i didn't include that header in the code just for shits and giggles, lol. also, make sure the user you run apache (and thus php) as has read permissions to the files in the directory.
Quote : | "However, evan, the problem is that the PHP script you provided earlier looks to be too manual. I was just using a query and a while loop to dynamically generate a list of links (where the link points to the file and the link text is the title of the document) and there are far too many files to write them out individually. Also, the number and types of files will change on a pretty regular basis." |
it wasn't intended to be a finished script, just a construct to get you started
this is really easy, though... just stick the full filesystem path in the mysql row with the unique ID... instead of the switch case, just do a mysql_query with your unique ID as the where clause. then, have it read the file path associated with that and present that...
i get the feeling you don't know php very well. ]6/13/2009 12:47:42 AM |
vertigo Veteran 135 Posts user info edit post |
Quote : | "i get the feeling you don't know php very well." |
evan, I do not. Haha.
Everything that I do is for free for a non-profit that I volunteer for outside of my "normal" job. I know that I should pick up a book, but I don't have time. I find most of what I need online, but sometimes I don't know how to do certain things, which is when I come to TWW. Sometimes I will ask my brother if he is online, but sometimes he isn't and sometimes he doesn't know the answer to my questions. The non-profit doesn't really need all that I do, but I use it as an excuse to learn more and it makes their lives a bit easier.
I have another question, actually. I will use this thread, unless I don't get any responses.
Let's say I have a table with all of our users in it and I use a single SQL query to get all of them and all of their information:$result = mysql_query("SELECT * FROM table ORDER BY name ASC"); Let's also say I that, among other things, there are two fields in the table, one called "male" and one called "female". I don't know why there isn't a single field called "sex" in which they put a "M" or a "F" since I didn't make the table. But what I want to do is print out the results so that it looks like this:Males - John - Jim - Jack
Females - Jennifer - Jessica - Julie I was thinking that I could use something like this to do it, but it doesn't work:$row = mysql_fetch_assoc($result); if($row['male'] == 1) { echo "<h2>Males</h2>"; echo "<ul>"; while ($row = mysql_fetch_assoc($result)) { echo "<li>".$row['name']."</li>"; } echo "</ul>"; } if($row['female'] == 1) { echo "<h2>Females</h2>"; echo "<ul>"; while ($row = mysql_fetch_assoc($result)) { echo "<li>".$row['name']."</li>"; } echo "</ul>"; } If I get rid of the if statements, it prints out, but everyone shows up together. If I use separate queries for both, then of course I can get it work. I just thought there might be a way to pull all the results with a single query and then sort them. Also, part of what I was trying to do is that if there were no males at the shelter at the time, then it wouldn't even print out the "Males" heading (or that it would alternatively say "no males at current" or something). Any help or suggestions you can provide would be great!
Thanks!
[Edited on June 16, 2009 at 3:39 PM. Reason : oops]6/16/2009 3:36:30 PM |
evan All American 27701 Posts user info edit post |
you're using mysql_fetch_assoc() twice - every call you make to that function advances the pointer forward by one record.
many ways to do this, but this is how i would do it:
$theNames = Array();
while ($theRow = mysql_fetch_assoc($theResult)) { if ($theRow['male'] == true) { $theNames['males'][] = $theRow['name']; } elseif ($theRow['female'] == true) { $theNames['females'][] = $theRow['name']; } }
$outBuffer = false;
if (array_key_exists('males',$theNames)) { $outBuffer .= "<h2>Males (" . count($theNames['males'] . ")</h2>\n"; $outBuffer .= "<ul>\n"; foreach ($theNames['males'] as $theName) { $outBuffer .= "<li>$theMale</li>\n"; } $outBuffer .= "</ul>\n"; } else { $outBuffer .= "<i><font color=red>No males currently in shelter.</font></i><br />\n"; }
if (array_key_exists('females',$theNames)) { $outBuffer .= "<h2>Females (" . count($theNames['females'] . ")</h2>\n"; $outBuffer .= "<ul>\n"; foreach ($theNames['females'] as $theName) { $outBuffer .= "<li>$theFemale</li>\n"; } $outBuffer .= "</ul>\n"; } else { $outBuffer .= "<i><font color=red>No females currently in shelter.</font></i><br />\n"; }
echo $outBuffer; ]6/16/2009 11:29:09 PM |
vertigo Veteran 135 Posts user info edit post |
evan, thank you for the suggestion! I'll try that out this afternoon and see what happens. 6/17/2009 8:09:51 AM |
evan All American 27701 Posts user info edit post |
oh, btw, you don't need the "== true" parts of those first if/then statements, i think i just put them there because you did
by default, the if conditional evaluates to true if the var/function in question is anything but 0, false, null, or the empty string (and yes, there's a difference, but only if you use ===).
so "if ($theRow['male'])" would work just fine.] 6/17/2009 9:48:50 AM |