Thursday, October 24, 2013

Adding a membership file-area with protected files

In this brief post I'll go over how to create a file-area for members-only, how to make files available for download while keeping them secure so that non-members can not download them, we're assuming that anyone who is logged in should have access to the files.

Assumptions: There are a few assumptions here, and that's just because this is not a thorough tutorial, but more aimed at helping out someone who's relatively new but has some experience.

The assumptions are that there is a datasource called 'fileDSN' which points to a database with a table called 'files' with the columns 'file_id' and 'name' in it, we assume the folder 'C:\SecureFiles' exists and contains the files you want to handle, and we assume that the folder is not under the webroot, and lastly we're assuming that there is a login function based on cflogin.

The code here is tested on OpenBD 2.0.2

Since this is a quick example rather than a full application tutorial I'll jump straight into the file-handling page.

We'll be using a database and two files, one for displaying files and links, and one for actually serving up the file.

We could have used cfdirectory to list what files are in the folder and then used a link with the filename, but that opens up a security hole, so instead we've put the filenames into a database table, all the information we need is the filename and the record ID, that way the only variable we take from the outside is the ID number for the record.

Create a file called filedisplay.cfm and paste the following code into it:



What we're doing is running a query to get a list of all the files we've added to the database.
We then create links for each file, using their ID and name, the that handles getting the file is described below.

Create a file called file.cfm and paste the following code into it:



This is a .CFM file, how can it handle downloads of other file types?
By using the cfheader and cfcontent tags to define the header and the content of the file presented to the browser.
Doing this, the file temporarily "becomes" the file we want to download instead of a usual .CFM file.

First we check if the user is logged in, because we decided earlier that you have to be logged in to get access to the files, and we check to make sure the url.id variable exists, if you're not logged in or the variable does not exist, the user is forwarded to index.cfm.

Then we run the query to get the file information, we use cfqueryparam to protect against some hack attempts, what this one does is make sure the url.id is a number, is at most 3 characters long (Which gives us up to 999 files to pull from the database), for a full description of how cfqueryparam works I point you to Adobes online documentation.

We then get into the actual file-handling, after running the query we check to see if it returns a single record which is what we want, if it doesn't we assume the file does not exist and output the text in the tag.

Next we set the folder to check in, which in this example is the C:\SecureFiles folder, we then run another cfif, this time to see if the file exists, and assuming it does we go on to set the fileInfo variable with information about the file which we'll use in a bit.

After that we set the mimeType variable and here we use the getPageContext tag which pulls information from an underlying JAVA function to get mime information about the file.

And then we set the header and content information by pulling it all together by outputting the various variables into the cfheader and cfcontent functions.

This is what transforms our .CFM file to whatever file it is we want to download, all these things are necessary for the browser to recognize what kind of file it's handling and what to do with it.

If it seems longwinded or complicated, it's not, just start playing with it and you'll get it.

So why are we doing this instead of creating a hidden folder somewhere and just link to the files?

Because security by obscurity is silly, it only takes one person to spread the link and anyone could download the file, doing it this way we can put in place various checks and balances, in this example we just make sure the user is logged in but it's easy to start adding other criteria or access levels.

I hope this short example is useful to someone, and remember I don't claim to present best coding practices or the best way to do anything, I'm just putting this out there as an example that might get someone going in the right direction :)

2 comments:

  1. Hi Marcus,

    I'm trying to get this concept to work with my flash player. When I click on the player, I want to use the code from file.cfm that you have above to get the path from the database and play the song.

    The main reason for this is to secure the URL from the source code (if anyone looks) in the flash player.

    Can you help me get this work?

    My player has this code:

    param name="FlashVars" value="mp3=listen.cfm?songID=#songID#&listen=yes&width=25&volume=50&showslider=0"

    And listen.cfm looks like this:

    cfset profileSng = cfcs.crm.searchSongs(songID=#url.songID#)>
    cfset songlink = profileSng.songlink>
    cfset songPath = "#application.path#/#songlink#">

    cfheader name="Content-Disposition" value="inline; filename=#songlink#">
    cfcontent type="application/mpeg" file="#songPath#" deletefile="No" reset="yes">

    My test page is here: http://www.thechristianjukebox.com/artistProfile.cfm?view=detail&artistID=49

    Thanks,

    -- Simone

    ReplyDelete
  2. Since you're not using other access control, it's incredibly hard to keep people from figuring out how to download the songs.

    In my example above we use the login function as access control, but if you're logged in you can grab the url and download the file.

    There ARE ways to protect the songs, but it gets really complex and probably more hassle than its worth.

    The only thing you can reasonably do is check if the person is supposed to be able to access the song (Which includes playing it through your flash player), but the player somehow needs to access the file to play it, and since all the player-magic happens on the *client side* it's nearly impossible to stop people from accessing the same file they're playing.

    I wish I had a better answer for you, but without putting a bunch of time into researching and testing I don't.

    ReplyDelete