We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 23214
    • 36 Posts
    Hi there,

    I’m building a members only website MODx Revolution.

    I can protect specific resources from unauthorised users by placing them in resource groups, but can anyone suggest a way to protect images and files uploaded to the website? The website will include high resolution images which should not be accessible to the public.

    I’ve come across FileLister in the past which has an option that limits the downloads to authorised users only (see ’requireAuthDownload’ option of http://rtfm.modx.com/display/ADDON/FileLister.FileLister). However, I do not need file listing capability. Ideally I need a snippet of some sort that I can pass a file path to, and have it give me a protected <a href="..."> link.

    Alternatively, I could build a standalone php script that controls access to these files. However, I have no idea how I could determine whether a user has successfully logged into the website or not.

    I hope this makes sense!
    thanks for any suggestions.
      • 3749
      • 24,544 Posts
      I think there may be a better solution, but in case you need to roll your own:

      If you are only using the default web context (and if you don’t know, then you are), the following code will tell you if the current user is logged in to the front end:
      <?php
       $isAuthenticated = $modx->user->hasSessionContext('web');

      If you are using multiple contexts and want to know if the user is logged in to the current context, you can do this:
      <?php
       $isAuthenticated = $modx->user->hasSessionContext($modx->context->get('key'));

      If you are using multiple contexts and want to know if the user is logged in to any of them, this code will do it:
      <?php
       $isAuthenticated = $modx->user->hasSessionContext(array('context1', 'context2', 'context3'));

        Did I help you? Buy me a beer
        Get my Book: MODX:The Official Guide
        MODX info for everyone: http://bobsguides.com/modx.html
        My MODX Extras
        Bob's Guides is now hosted at A2 MODX Hosting
        • 23214
        • 36 Posts
        Hi Bob,

        Thanks for your reply. An first of all I’d like to say a massive thank you for your MODx guides, they’ve been invaluable to me.

        Regarding the use of $modx->user->hasSessionContext(’web’), wouldn’t I have to execute this from within a snippet? I’m not sure if its possible to access the $modx object from a standalone php script.

        I’ve tried building a snippet which can pass through file downloads (using the php readfile() command). This snippet is then called from a resource. This all appears to work fine, the problem I have is that I am unable to override the headers based on the file type being retreived.

        This is an example of what I’m using in my snippet atm.
        // Streaming the file may take a while
        set_time_limit(0);
        
        // Stream!
        header('Content-Description: File Transfer');
        header('Content-Type: '.$filemime);
        header('Content-Disposition: inline; filename='.$filename);
        header('Content-Transfer-Encoding: binary');
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
        header('Cache-Control: no-store, no-cache, must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($filepath));
        ob_clean();
        ob_end_flush();
        readfile($filepath);
        
        return "";


        The headers are always set to text/html regardless of the value set in $filemime. I guess this is because the Content Type set within the resource overrides anything the snippet does. I’m not sure if you know any way around this. Or perhaps I’m going about this completely wrong? I’ve used code similar to this in standalone scripts without an issue before.
          • 23214
          • 36 Posts
          I’ve managed to get my snippet working now.

          All I needed was to exit() immediately after the readfile() command. That way the mime headers don’t get overriden later on.
            • 27672
            • 168 Posts
            phatphug, how exactly are you using this? on the page where you call your snippet, what settings did you do to get the file to download? did you have to alter "Uses Template", "Resource Type", "Internet Media Type" or "Content-Disposition" to make it work properly?

            are you passing $filepath in via request variable? or hard coding into specific resources?
              • 27672
              • 168 Posts
              i got it working using:
              [!Test_FileTest? &filename=`swine-flu.pdf` &filemime=`application/pdf` &filepath=`/www/local/sites/www018/public_html/assets/files/swine-flu.pdf`!]

              for example, with the settings:
              Uses Template: (blank)
              Resource Type: Web page
              Internet Media Type: text/html
              Content-Disposition: Either Inline or Attachment
                • 23214
                • 36 Posts
                Hi slush puppie

                Glad you got it working. With the resource I created I used a blank template and left everything else as default. The header() calls made from the snippet will set the media (mime) type and content disposition dynamically based on what the snippet is given.

                In my snippet I only accept one parameter (the file being requested). The snippet takes this from the $_REQUEST array instead of using a snippet parameter, that way I can call my resource directly like: http://localhost/file-retriever.html?fd=assets/files/file.jpg.

                One important thing to be aware of is checking the path requested isn’t going anywhere it shouldn’t. I’m using php’s realpath function (http://php.net/manual/en/function.realpath.php) for this. Otherwise someone could request a file like: /home/server/public_html/assets/files/../../core/config.inc.php, or something along those lines.

                I’m at home atm, but I can post my full snippet tomorrow if that helps. I’m not sure if what I’m doing is the most efficient/cleanest way of doing things, but it works for my purposes.
                  • 23214
                  • 36 Posts
                  If anyone is interested, the full code of my snippet is below.
                  <?php
                  // filemanager_retreiver snippet
                  // Andrew Pettican November 2010
                  // 
                  // This script retreives the requested file from assets/filemanager/ directory.
                  // 
                  // GET Parameters
                  // - fd (File Descriptor)
                  //   The path to the requested file. NB: this must be a file which exists in the assets/filemanager/ directory, but 
                  //   it can be supplied in pretty much any format:
                  //   Example 1. contacts/joe_bloggs.jpg
                  //   Example 2. assets/filemanager/contacts/joe_bloggs.jpg
                  //   Example 3. /home/server/public_html/informationcentre/assets/filemanager/contacts/joe_bloggs.jpg
                  //   Example 4. http://localhost/assets/filemanager/contacts/joe_bloggs.jpg
                  //
                  // - dl (Download Flag, optional, default: false)
                  //   Identifies if the file should be supplied to the browser as a download or inline, to the browser page.
                  //////////////////////////////////////////////////////////////////////
                  
                  mb_internal_encoding("UTF-8");
                  $output = "";
                  
                  // Fetch parameters
                  // Leading '/' '\' '<' '>' '$' and '.' chars will be removed from the requested path
                  $file_path = ltrim($modx->getOption('fd',$_REQUEST,false), "/\\.<>$");
                  
                  // Flag indicating if this file should load as a download.
                  $file_download = ($modx->getOption('dl',$_REQUEST,false) == "1");
                  
                  
                  // Settings
                  $filemanager_path = "assets/filemanager/";
                  $base_path = $modx->getOption('base_path').$filemanager_path;
                  $base_url = $modx->getOption('site_url').$filemanager_path;
                  $debug_mode = FALSE;
                  
                  
                  // Accepted file types and their mime's
                  // See: http://www.w3schools.com/media/media_mimeref.asp
                  // See: http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types
                  $accepted_file_types = array();
                  
                  // Images
                  $accepted_file_types['jpg'] = "image/jpeg";
                  $accepted_file_types['jpeg'] = "image/jpeg";
                  $accepted_file_types['gif'] = "image/gif";
                  $accepted_file_types['png'] = "image/png";
                  $accepted_file_types['bmp'] = "bmp";
                  $accepted_file_types['tif'] = "image/tiff";
                  $accepted_file_types['tiff'] = "image/tiff";
                  
                  // Data
                  $accepted_file_types['zip'] = "application/zip";
                  $accepted_file_types['eps'] = "application/postscript";
                  
                  // Audio/Video
                  $accepted_file_types['wmv'] = "video/x-ms-wmv";
                  $accepted_file_types['mov'] = "video/quicktime";
                  $accepted_file_types['flv'] = "video/x-flv";
                  $accepted_file_types['mp4'] = "video/mp4";
                  $accepted_file_types['webm'] = "video/webm";
                  $accepted_file_types['ogv'] = "video/ogg";
                  $accepted_file_types['ogg'] = "video/ogg";
                  $accepted_file_types['mp3'] = "audio/mpeg";
                  
                  
                  // Debug
                  if($debug_mode) {
                  	$output .= "<!-- Path/url requested : '$file_path' -->\n";
                  }
                  
                  
                  // Validation - stage 1
                  // Cut $filemanager_path, $base_path or $base_url from the start of the file path if it exists
                  $filemanager_path_len = mb_strlen($filemanager_path);
                  if(mb_strpos($file_path, $filemanager_path) === 0)
                  {
                  	$file_path = mb_substr($file_path, mb_strlen($filemanager_path));
                  }
                  
                  if(mb_strpos("/".$file_path, $base_path) === 0)
                  {
                  	$file_path = mb_substr("/".$file_path, mb_strlen($base_path));
                  }
                  
                  if(mb_strpos($file_path, $base_url) === 0)
                  {
                  	$file_path = mb_substr($file_path, mb_strlen($base_url));
                  }
                  
                  if($debug_mode) {
                  	$output .= "<!-- (V1) Local path requested : '$file_path' -->\n";
                  }
                  
                  
                  // Validation - stage 2
                  // Determine the real path to the file, if the real path is different to the actual path, it could be v dangerous:
                  // e.g. assets/filemanager/../../../../restricted/dir/above/web/root.jpg
                  $file_path = $base_path.$file_path;
                  $real_path = realpath($file_path);
                  $validation_passed = ($real_path == $file_path);
                  if($debug_mode) {
                  	$output .= "<!-- (V2) Real Path Check : '$real_path' == '".$file_path."' => ".($validation_passed ? "Y" : "N")." -->\n";
                  }
                  
                  
                  // Validation - stage 3
                  // Sanity check, ensure the $real_path begins with $base_path
                  $validation_passed = ($validation_passed && ($base_path == mb_substr($real_path, 0, mb_strlen($base_path))));
                  if($debug_mode) {
                  	$output .= "<!-- (V3) Sanity Path Check : '$base_path' == '".mb_substr($real_path, 0, mb_strlen($base_path))."' => ".($validation_passed ? "Y" : "N")." -->\n";
                  }
                  
                  
                  // Fetch some file info from the path
                  $file_info = pathinfo($real_path);
                  $file_name = $file_info['basename'];
                  $file_ext = mb_strtolower($file_info['extension']);
                  $file_mime = "application/octet-stream";
                  
                  
                  // Validation - stage 4
                  // Ensure the file exists and has an accepted file extension
                  $validation_passed = ($validation_passed && $real_path != "" && is_file($real_path) && array_key_exists($file_ext, $accepted_file_types));
                  if($validation_passed)
                  {
                  	$file_mime = $accepted_file_types[$file_ext];
                  }
                  
                  
                  // Debug
                  if($debug_mode)
                  {
                  	$output .= "
                  	<!--
                  		file_path : '$file_path'
                  		real_path : '$real_path'
                  		file_name : '$file_name'
                  		file_ext : '$file_ext'
                  		file_mime : '$file_mime'
                  		validation_passed : '".($validation_passed ? "Y" : "N")."'
                  	-->
                  	";
                  }
                  
                  
                  // Continue?
                  //$validation_passed = FALSE; // Debug
                  if($validation_passed)
                  {
                  	// Streaming the file may take a while
                  	set_time_limit(0);
                  	
                  	// Stream!
                  	header('Content-Description: File Transfer');
                  	header('Content-Type: '.$file_mime);
                  	header('Content-Disposition: '.($file_download ? 'attachment' : 'inline').'; filename='.$file_name);
                  	header('Content-Transfer-Encoding: binary');
                  	header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
                  	header('Cache-Control: no-store, no-cache, must-revalidate');
                  	header('Pragma: public');
                  	header('Content-Length: ' . filesize($real_path));
                  	ob_clean();
                  	ob_end_flush();
                  	readfile($real_path);
                  	exit();
                  }
                  else
                  {
                  	$output .= "Cannot read file";
                  }
                  
                  return $output;
                  ?>


                  I then created a resource in the document tree, using a blank template. This simply contains a single call to the snippet:
                  [[!filemanager_retreiver]]

                  You can then do the following:
                  http://localhost/filemanager-retreiver.html?fd=contacts/joe_bloggs.jpg
                  This will serve the file inline to the browser. This allows you to do something like <img src="filemanager-retreiver.html?fd=contacts/joe_bloggs.jpg" alt="" />

                  http://localhost/filemanager-retreiver.html?fd=contacts/joe_bloggs.jpg&dl=1
                  This will serve the file to the browser as a download rather than inline.

                  I’ve also tried to make it flexible, so that the following requests all return the same file
                  1. http://localhost/filemanager-retreiver.html?fd=contacts/joe_bloggs.jpg
                  2. http://localhost/filemanager-retreiver.html?fd=assets/filemanager/contacts/joe_bloggs.jpg
                  3. http://localhost/filemanager-retreiver.html?fd=/home/server/public_html/informationcentre/assets/filemanager/contacts/joe_bloggs.jpg
                  4. http://localhost/filemanager-retreiver.html?fd=http://localhost/assets/filemanager/contacts/joe_bloggs.jpg

                  Currently I use a hardcoded array of file types, only file extensions in this array will be returned (see $accepted_file_types).

                  I’ve also setup a .htaccess rule so that all requests to assets/filemanager/* get rerouted through http://localhost/filemanager-retreiver.html?fd=assets/filemanager/* ... Pretty neat I think smiley

                  # Pass Filemanager Requests through filemanager-retreiver.html
                  RewriteCond %{REQUEST_URI} assets/filemanager/*
                  RewriteRule ^(.*)$ filemanager-retreiver.html?fd=$1 [L,QSA]



                  Like I said earlier, I’m sure there are more efficient/cleaner ways of achieving what I want, but this is working pretty well for me. I’m open to suggestions for improvement though.

                  To ensure files cannot be read by unauthorised users, I believe it should be sufficient to set access permissions on the filemanager-retreiver.html document. I suppose the snippet could be modified to perform additional checks too.
                    • 27672
                    • 168 Posts
                    thanks for posting the code, i like the looks of this snippet a lot. i am using this in evo (1.0.4), so i had to make a few changes, since $modx->getOption is not available:
                    <?php
                    ini_set('display_errors', '1'); 
                    /* filemanager_retreiver snippet
                     * Andrew Pettican November 2010
                     * 
                     * This script retreives the requested file from assets/filemanager/ directory.
                     * 
                     * GET Parameters
                     * - fd (File Descriptor)
                     *   The path to the requested file. NB: this must be a file which exists in the assets/filemanager/ directory, but 
                     *   it can be supplied in pretty much any format:
                     *   Example 1. contacts/joe_bloggs.jpg
                     *   Example 2. assets/filemanager/contacts/joe_bloggs.jpg
                     *   Example 3. /home/server/public_html/informationcentre/assets/filemanager/contacts/joe_bloggs.jpg
                     *   Example 4. http://localhost/assets/filemanager/contacts/joe_bloggs.jpg
                     *
                     * - dl (Download Flag, optional, default: false)
                     *   Identifies if the file should be supplied to the browser as a download or inline, to the browser page.
                     */
                    
                    mb_internal_encoding("UTF-8");
                    $output = "";
                    
                    ## GET OPTIONS IN EVO
                    $getOptionsEvo = array();
                    $getOptionsEvo['fd'] = (isset($_REQUEST['fd'])) ? $_REQUEST['fd'] : false;
                    $getOptionsEvo['dl'] = (isset($_REQUEST['dl'])) ? $_REQUEST['dl'] : false;
                    
                    ## Fetch parameters
                    ## Leading '/' '\' '<' '>' '$' and '.' chars will be removed from the requested path
                    //$file_path = ltrim($modx->getOption('fd',$_REQUEST,false), "/\\.<>$");
                    $file_path = ltrim($getOptionsEvo['fd'], "/\\.<>$");
                    
                    ## Flag indicating if this file should load as a download.
                    //$file_download = ($modx->getOption('dl',$_REQUEST,false) == "1");
                    $file_download = ($getOptionsEvo['dl'] == "1");
                    
                    ## Settings
                    $filemanager_path = "assets/filemanager/";
                    $base_path = $modx->config['base_path'].$filemanager_path;
                    $base_url = $modx->config['site_url'].$filemanager_path;
                    $debug_mode = FALSE;
                    
                    ## Accepted file types and their mime's
                    ## See: http://www.w3schools.com/media/media_mimeref.asp
                    ## See: http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types
                    $accepted_file_types = array();
                    
                    ## Images
                    //$accepted_file_types['jpg'] = "image/jpeg";
                    //$accepted_file_types['jpeg'] = "image/jpeg";
                    //$accepted_file_types['gif'] = "image/gif";
                    //$accepted_file_types['png'] = "image/png";
                    //$accepted_file_types['bmp'] = "bmp";
                    //$accepted_file_types['tif'] = "image/tiff";
                    //$accepted_file_types['tiff'] = "image/tiff";
                    
                    ## Data
                    $accepted_file_types['zip'] = "application/zip";
                    //$accepted_file_types['eps'] = "application/postscript";
                    $accepted_file_types['pdf'] = "application/pdf";
                    $accepted_file_types['doc'] = "application/msword";
                    
                    ## Audio/Video
                    //$accepted_file_types['wmv'] = "video/x-ms-wmv";
                    //$accepted_file_types['mov'] = "video/quicktime";
                    //$accepted_file_types['flv'] = "video/x-flv";
                    //$accepted_file_types['mp4'] = "video/mp4";
                    //$accepted_file_types['webm'] = "video/webm";
                    //$accepted_file_types['ogv'] = "video/ogg";
                    //$accepted_file_types['ogg'] = "video/ogg";
                    //$accepted_file_types['mp3'] = "audio/mpeg";
                    
                    ## Debug
                    if($debug_mode){
                    	$output .= "<!-- Path/url requested : '$file_path' -->\n";
                    }
                    
                    ## Validation - stage 1
                    ## Cut $filemanager_path, $base_path or $base_url from the start of the file path if it exists
                    $filemanager_path_len = mb_strlen($filemanager_path);
                    if(mb_strpos($file_path, $filemanager_path) === 0){
                    	$file_path = mb_substr($file_path, mb_strlen($filemanager_path));
                    }
                    
                    if(mb_strpos("/".$file_path, $base_path) === 0){
                    	$file_path = mb_substr("/".$file_path, mb_strlen($base_path));
                    }
                    
                    if(mb_strpos($file_path, $base_url) === 0){
                    	$file_path = mb_substr($file_path, mb_strlen($base_url));
                    }
                    
                    if($debug_mode){
                    	$output .= "<!-- (V1) Local path requested : '$file_path' -->\n";
                    }
                    
                    ## Validation - stage 2
                    ## Determine the real path to the file, if the real path is different to the actual path, it could be v dangerous:
                    ## e.g. assets/filemanager/../../../../restricted/dir/above/web/root.jpg
                    $file_path = $base_path.$file_path;
                    $real_path = realpath($file_path);
                    $validation_passed = ($real_path == $file_path);
                    if($debug_mode){
                    	$output .= "<!-- (V2) Real Path Check : '$real_path' == '".$file_path."' => ".($validation_passed ? "Y" : "N")." -->\n";
                    }
                    
                    ## Validation - stage 3
                    ## Sanity check, ensure the $real_path begins with $base_path
                    $validation_passed = ($validation_passed && ($base_path == mb_substr($real_path, 0, mb_strlen($base_path))));
                    if($debug_mode){
                    	$output .= "<!-- (V3) Sanity Path Check : '$base_path' == '".mb_substr($real_path, 0, mb_strlen($base_path))."' => ".($validation_passed ? "Y" : "N")." -->\n";
                    }
                    
                    ## Fetch some file info from the path
                    $file_info = pathinfo($real_path);
                    $file_name = $file_info['basename'];
                    $file_ext = mb_strtolower($file_info['extension']);
                    $file_mime = "application/octet-stream";
                    
                    ## Validation - stage 4
                    ## Ensure the file exists and has an accepted file extension
                    $validation_passed = ($validation_passed && $real_path != "" && is_file($real_path) && array_key_exists($file_ext, $accepted_file_types));
                    if($validation_passed){
                    	$file_mime = $accepted_file_types[$file_ext];
                    }
                    
                    ## Debug
                    if($debug_mode){
                    	$output .= "
                    	<!--
                    		file_path : '$file_path'
                    		real_path : '$real_path'
                    		file_name : '$file_name'
                    		file_ext : '$file_ext'
                    		file_mime : '$file_mime'
                    		validation_passed : '".($validation_passed ? "Y" : "N")."'
                    	-->
                    	";
                    }
                    
                    ## Continue?
                    ##$validation_passed = FALSE; // Debug
                    if($validation_passed){
                    	## Streaming the file may take a while
                    	set_time_limit(0);
                    	
                    	## Stream!
                    	header('Content-Description: File Transfer');
                    	header('Content-Type: '.$file_mime);
                    	header('Content-Disposition: '.($file_download ? 'attachment' : 'inline').'; filename='.$file_name);
                    	header('Content-Transfer-Encoding: binary');
                    	header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
                    	header('Cache-Control: no-store, no-cache, must-revalidate');
                    	header('Pragma: public');
                    	header('Content-Length: ' . filesize($real_path));
                    	ob_clean();
                    	ob_end_flush();
                    	readfile($real_path);
                    	exit();
                    }else{
                    	$output .= "Cannot read file";
                    }
                    
                    return $output;
                    ?>


                    will probably keep playing with this, but i can see it being very useful.
                      • 27672
                      • 168 Posts
                      i think the next alteration i’m going to work on is locking the files down to certain web user groups.

                      so within /assets/filemanager/ i would have directories for /ThisSchoolBoard/, /ThatSchoolBoard/, /YetAnotherSchoolBoard/, ... and then do a validation to make sure the web user is logged in as a member of an appropriately named WUG. if/when i get this accomplished, i’ll post back here with it.