Search

gents, i was wondering if anyone has had any experience with this? i’m using a modified front end authentication and would like for users to be able to upload files that were not accessible, even by uri, by other users.

so far, all i can come up with is a custom data source to access a mysql database with BLOBs - but that may require more time than the project allows, so i’d like to avoid it if i can.

Considering that PHP (which would need read access to the files) is usually running as the same user as apache (which you’d need to block from accessing the files), this may be tricky. What kind of access do you have to your server? Could you store the files outside the web root?

yes, we have access to the homedir.

i’m beginning to think that the best way would be to extend field.upload to a new blob field.

You could use the normal upload field and protect the upload folder using an .htaccess rule. Create a .htaccess file in the upload directory with the contents:

deny from all

This will prevent the files being reachable via HTTP. The retrieve the files you can serve them using an intermediary PHP script.

i just found this thread where nils is trying to do the exact same thing.

i really like your .htaccess idea, nick. i haven’t done a lot with mixing symphony and straight up php, so i imagine the security on the intermediary page will just be a matter of checking the session for whether or not someone is logged in.

of course, that’s only IF i can figure out how to get into symphony’s session.

If you’re hiding the files by .htaccess, and using authentication in Symphony, then wouldn’t a little customisation of a Symphony ds to check for authentication before serving a file list work? I’m probably missing something though…

.htaccess isn’t just hiding the files, it’s making them completely inaccessible by http. this way, they can only be accessed by a php script which will serve up headers and readfile() the contents, essentially turning the php script into the file’s contents. this appears to be the most secure way to store files, as it guarantees that nobody, even someone with a url directly to the file, can access them unless they are authenticated through sessions/cookies.

the road block i’m hitting is that my external php script has no way of knowing whether or not the user has been authenticated. i’ve modified front-end-authentication to put extra information into $context[‘params’] and into $_SESSION, but have not yet found a way to access any of symphony’s saved session variables from the outside.

but have not yet found a way to access any of symphony’s saved session variables from the outside.

You don’t need to be “outside”. You may attach an event to a Symphony page like the following (using GET params — you might add some .htaccess rerwites):

<?php

    Class eventdownload_file extends Event{

        public static function about(){
            return array(
                     'name' => 'Download File',
                     'author' => array(
                            'name' => 'Michael Eichelsdoerfer',
                            'website' => 'http://michael-eichelsdoerfer.de',
                            'email' => 'info@michael-eichelsdoerfer.de'),
                     'version' => '1.0',
                     'release-date' => '2008-12-26T14:18:14+00:00',
                     'trigger-condition' => 'GET request'); 
        }

        public static function documentation(){
            return '
        <h3>Download File Event</h3>
        <p>This event is used to restrict file downloads in Symphony to logged-in users.</p>
        <p>You should prevent direct access to your upload directory with htaccess:</p>
        <pre><code>Order Deny,Allow
Deny from all</code></pre>
        <p>This event will be triggered via GET request. Your download path should be s.th. like:</p>
        <pre><code>http://www.yourdomain.com/?path=uploads&amp;file=secret-image.jpg</code></pre>
        <p>Please note that the <code>workspace</code> part of the actual file URL is hardcoded in the event for security reasons.</p>
        ';
        }

        public function load(){         
            $loggedin = $this->_Parent->isLoggedIn();
            if(isset($_GET['path']) && isset($_GET['filename'])){
                if($loggedin){
                    return $this->__trigger();
                }
            }
            else {
                return;
            }
        }

        protected function __trigger(){
            $path = $_GET['path'];
            $filename = $_GET['filename'];
            $workspace = "workspace";
            $uploaddir = "uploads";

            ## path MUST contain the name of the upload directory
            if(strpos($path,$uploaddir) === false) exit;

            $file = $workspace.'/'.$path.'/'.$filename;
            $filesize = filesize($file);
            $mimeType = $this->returnMIMEType($filename);

            if ($mimeType == 'application/msword' ||
                $mimeType == 'application/vnd.ms-excel' ||
                $mimeType == 'application/vnd.ms-powerpoint' ||
                $mimeType == 'application/pdf'){
                header("Content-Disposition: attachment; filename=".$filename);
            }
            else{
                header("Content-Disposition: inline; filename=".$filename);
            }
            header("Content-type: ".$mimeType);
            header("Content-Length: ".$filesize);
            $result = @readfile($file);
            return $result;
        }

        function returnMIMEType($filename){
            preg_match("|.([a-z0-9]{2,4})$|i", $filename, $fileSuffix);
            switch(strtolower($fileSuffix[1])){
                case "js" :
                    return "application/x-javascript";
                case "json" :
                    return "application/json";
                case "jpg" :
                case "jpeg" :
                case "jpe" :
                    return "image/jpeg";
                case "png" :
                case "gif" :
                case "bmp" :
                case "tiff" :
                    return "image/".strtolower($fileSuffix[1]);
                case "css" :
                    return "text/css";
                case "xml" :
                    return "application/xml";
                case "doc" :
                case "docx" :
                    return "application/msword";
                case "xls" :
                case "xlt" :
                case "xlm" :
                case "xld" :
                case "xla" :
                case "xlc" :
                case "xlw" :
                case "xll" :
                    return "application/vnd.ms-excel";
                case "ppt" :
                case "pps" :
                    return "application/vnd.ms-powerpoint";
                case "rtf" :
                    return "application/rtf";
                case "pdf" :
                    return "application/pdf";
                case "html" :
                case "htm" :
                case "php" :
                    return "text/html";
                case "txt" :
                    return "text/plain";
                case "mpeg" :
                case "mpg" :
                case "mpe" :
                    return "video/mpeg";
                case "mp3" :
                    return "audio/mpeg";
                case "wav" :
                    return "audio/wav";
                case "aiff" :
                case "aif" :
                    return "audio/aiff";
                case "avi" :
                    return "video/msvideo";
                case "wmv" :
                    return "video/x-ms-wmv";
                case "mov" :
                    return "video/quicktime";
                case "zip" :
                    return "application/zip";
                case "tar" :
                    return "application/x-tar";
                case "swf" :
                    return "application/x-shockwave-flash";
                default :
                    if(function_exists("mime_content_type")){
                        $fileSuffix = mime_content_type($filename);
                    }
                    return "unknown/" . trim($fileSuffix[0], ".");
            }
        }

    }

that’s fantastic, michael. thank you much.

not only was this helpful, but it gives me a good, easy look into custom events. i have one question, though.

the script uses _Parent->isLoggedIn(), which apparently tracks whether or not you’re logged into the back end. since i will use a front-end login, how can i go about testing for other criteria? being able to access the page’s xml would be my first choice, especially because each file is restricted to one user (not just everyone with a login), but aren’t events fired before the data sources are loaded?

Hmmm, I never used the Frontend Authentication extension. But my first guess would be to check for the FRONT_END_AUTHENTICATION_SUCCESSFUL constant. I guess you might as well “manually” check for a valid session or cookie (as it is done in the extension starting from line 198). This code should work outside of the extension’s scope as well, because it uses rather generic PHP functions.

So I would try this first:

if(defined('FRONT_END_AUTHENTICATION_SUCCESSFUL'))
{
    if(FRONT_END_AUTHENTICATION_SUCCESSFUL == true)
    {
        ...do stuff here...
    }
}

Or maybe this:

## Check for a session, and validate it
if(isset($_SESSION[__SYM_COOKIE_PREFIX_ . 'front-end-authentication'])){
    $username = $_SESSION[__SYM_COOKIE_PREFIX_ . 'front-end-authentication']['username'];
    $password = $_SESSION[__SYM_COOKIE_PREFIX_ . 'front-end-authentication']['password'];
    if($this->__validate($username, $password, true))
    {
        define_safe('AUTHENTICATED', true);
    }
}

## Check for a cookie, and validate it
elseif(isset($_COOKIE[__SYM_COOKIE_PREFIX_ . 'front-end-authentication']))
{
    $username = $_COOKIE[__SYM_COOKIE_PREFIX_ . 'front-end-authentication']['username'];
    $password = $_COOKIE[__SYM_COOKIE_PREFIX_ . 'front-end-authentication']['password'];
    if($this->__validate($username, $password, true))
    {
        define_safe('AUTHENTICATED', true);
    }
}

if(defined('AUTHENTICATED'))
{
    if(AUTHENTICATED == true)
    {
        ...do stuff here...
    }
}

(Please note that I am by no means a PHP guy, so I might be damned wrong.)

this is great stuff. FRONT_END_AUTHENTICATION_SUCCESSFUL is already define_safe‘d by the extension’s __authenticate(), but i never realized i could use the extension’s defined constants in the custom event. this opens up worlds for me.

this opens up worlds for me

This Symphony thing is doing this to all of us, all the time.

:-)

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details