Class: SecureDownload
- Inherits:
-
Object
- Object
- SecureDownload
- Includes:
- Mongrel::HttpHandlerPlugin
- Defined in:
- lib/mongrel_secure_download/init.rb
Overview
Mongrel Secure Download Handler
The need to send secured files in a fast and reliable way is common.
Sending a file from inside of a web application can be slow and also utilizes an entire application thread/process until the user is done downloading the file. For large files this is inefficient. The other option is to have the web server itself send the files as normal static content. This is faster but means that the files have to be in a web accessible public directory so that if someone guessed the URI of a file they could gain access to it. This is not a reasonable solution for situations where files need to be secured against unauthorized downloading.
This handler addresses the problem of having a fast and secure download mechanism for web applications. The mechanism works by having the application generate a special URI containing a token that is only valid for a certain period of time. The server then recognizes this URI and generates a token using the parameters passed in and checks for a match before sending the file to the user. The key to the process is the secret string that both the server and the application are aware of.
URI Format
A properly formed URI will have a path and query of the following format
<uri-prefix>/?token=<token>&relative-path=<relative-path>×tamp=<timestamp>
Where
<uri-prefix> is a directory that does not exist in the directory structure of the application but does exist in the directory structure of the server.
example uri-prefix
/downloads
<relative-path> is the path to the file being requested, relative to the path at which the web server is running. The web server must have permissions to read the file.
example relative-path
/files/your_secured_file.txt
<timestamp> is the number of seconds since epoch until the time when this download expires
example timestamp (ruby on rails)
1.minute.from_now.to_i
<token> is the SHA1 hash of the concatenation of the following items:
-
the user defined secret string
-
the relative path to the file
-
the timestamp
Using the Handler
To use the handler you need to do the following:
Setup the handler within a configuration script and pass in the secret string.
example configuration script
uri “/downloads”, :handler => plugin(‘/handlers/securedownload’,=> “my_secret_string”)
In your application form a secured URI by creating the proper parameters and perform an SHA1 hash of the parameters to create the proper token
example code (ruby on rails)
require ‘digest/sha1’
secret_string = ‘my_secret_string’ uri_prefix = ‘/downloads’ relative_path = ‘/files/secret_document.pdf’ timestamp = 1.minute.from_now.to_i token = Digest::SHA1.hexdigest(secret_string + relative_path + timestamp) uri = “#uri_prefix/?token=#token&relative-path=#relative_path×tamp=#timestamp”
Start mongel by passing in the location of the configuration script from step 1 with the -S command line switch.
example mongrel start command
mongrel_rails start -S config/secure_download_config.rb
Error messages
If any of the parameters in the URI or the secret_string are missing the handler returns a 500 Application Error.
If the token passed in as a parameter does not match the token generated by the handler (if someone tries to guess the token) the handler returns a 403 Forbidden error.
If the timestamp is earlier than the current server time, meaning that the file is no longer a valid download then the handler returns a 408 Request Time-out Error. This error is not technically correct but it makes the most sense in the context of the handler.
Instance Method Summary collapse
Instance Method Details
#process(request, response) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mongrel_secure_download/init.rb', line 106 def process(request, response) query = Mongrel::HttpRequest.query_parse(request.params['QUERY_STRING']) if @options[:secret_string].nil? or query['token'].nil? or query['timestamp'].nil? or query['relative-path'].nil? response.start(500){} elsif query['timestamp'].to_i < Time.now.to_i response.start(408){} elsif query['token'] == Digest::SHA1.hexdigest("#{@options[:secret_string]}#{query['relative-path']}#{query['timestamp']}").to_s send_file(File.("." + query['relative-path']), response) else response.start(403){} end end |