My question is not specific to any coding issue but sort of guidance. I need to make pdf files (non password protected files) securely available for download so only authenticated users can download the file.
All I can think of is that store all the pdf files outside the root, and then copy the file to with a random generated name to a public place temporarily and make it available. Any comments?
or is there a way to protect the pdf files with a new password each time and inform the authenticated user the password before the download (half of the problem is users are forgetting the pdf password, so we are thinking for an alternate strategy).
Appreciate any advice or comments.
You can do it like this also:
use digest auth to login user before they can download
http://www.php.net/manual/en/features.http-auth.php
test.php
<?php
$realm = 'Restricted area';
// user => password
// this can come from your DB
$users = array('myuser' => 'mypass');
// Send Login Request
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Text to send if user hits Cancel button');
}
// analyze the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']]))
die('Wrong Credentials!');
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response)
die('Wrong Credentials!');
// ok, valid username & password
// send pdf file
$file_url = "http://fzs.sve-mo.ba/sites/default/files/dokumenti-vijesti/sample.pdf";
header('Content-Type: application/pdf');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=". basename($file_url));
readfile($file_url);
exit();
// function to parse the http auth header
function http_digest_parse($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
?>
If the users already authenticate into your system before they can download anything, you can bypass protecting the files individually, by having a php script "between" the user and the pdf.
Something like this (simplified for example purposes):
if ($_SESSION['user_authenticated']){
$pdf_info = read_pdf_metadata($pdf_id);
header('Content-type: application/pdf');
header("Content-disposition: filename=".$pdf_info['filename']);
echo file_get_contents($pdf_info['file_path']);
}
The headers are probably a little wonky (I haven't worked with them in awhile and am shooting from the hip on them), but that code is based on the idea of having a database table or other data source for PDF files stored outside the document root. Using something like that, you never have to send a direct link to the PDFs themselves.
If you want to let users get direct access to the PDF file (ie it's inside the document root), you could create an .htaccess inside the directory the PDFs are located in. For example, you could have:
RewriteEngine On
RewriteRule ^(.*) check.php?file=$1 [L]
Then, in check.php, you could have:
$file = realpath(__DIR__ . "/" . $_GET["file"]);
if(!preg_match("/^" . preg_quote(__DIR__, "/") . "/", $file))
{
// Prevent someone from using relative paths in the URL
die("Invalid file path");
}
elseif(!file_exists($file))
{
die("File doesn't exist");
}
elseif(!SOME_PERMISSION_CHECK)
{
die("You do not have access to these files");
}
header("content-type: application/pdf");
header("content-length: " . filesize($file), true);
readfile($file);