mongrel_secure_download 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2006 Josh Ferguson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included
11
+ in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2006 Josh Ferguson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included
11
+ in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README ADDED
@@ -0,0 +1,86 @@
1
+ == Mongrel_secure_download GemPlugin
2
+
3
+ The need to send secured files in a fast and reliable way is common.
4
+
5
+ Sending a file from inside of a web application can be slow
6
+ and also utilizes an entire application thread/process until the user
7
+ is done downloading the file. For large files this is inefficient.
8
+ The other option is to have the web server itself send the files as normal
9
+ static content. This is faster but means that the files have to be in a web
10
+ accessible public directory so that if someone guessed the URI of a file
11
+ they could gain access to it. This is not a reasonable solution for
12
+ situations where files need to be secured against unauthorized downloading.
13
+
14
+ This handler addresses the problem of having a fast and secure
15
+ download mechanism for web applications. The mechanism works by having
16
+ the application generate a special URI containing a token that is only
17
+ valid for a certain period of time. The server then recognizes this URI
18
+ and generates a token using the parameters passed in and checks for a match
19
+ before sending the file to the user. The key to the process is the secret string
20
+ that both the server and the application are aware of.
21
+
22
+
23
+ The URI generated has a path of the following format:
24
+
25
+ <uri-prefix>/?token=<token>&relative-path=<relative-path>&timestamp=<timestamp>
26
+
27
+ <uri-prefix> is a directory that does not exist in the directory structure of the application but does
28
+ exist in the directory structure of the server.
29
+ example: /downloads
30
+
31
+ <relative-path> is the path to the file being requested, relative to the path
32
+ at which the web server is running. The web server must have permissions to read the file.
33
+ example: /files/your_secured_file.txt
34
+
35
+ <timestamp> is the number of seconds since epoch until the time when this download expires
36
+ example (in ruby on rails): 1.minute.from_now.to_i
37
+
38
+ <token> is the SHA1 hash of the concatenation of the following items:
39
+ 1) the secret string defined in the configuration script
40
+ 2) the relative path to the file
41
+ 3) the timestamp
42
+
43
+
44
+ To use the plugin you need to do the following:
45
+
46
+ 1) setup the handler within a configuration script and pass in the secret string.
47
+
48
+ example configuration script:
49
+
50
+ uri "/download", :handler => plugin('/handlers/securedownload',{:secret_string => "my_secret_string"})
51
+
52
+ 2) In your application, form a secured URI by creating the proper parameters and
53
+ perform an SHA1 hash of the parameters to create the proper token
54
+
55
+ example code (ruby on rails):
56
+
57
+ require 'digest/sha1'
58
+
59
+ secret_string = 'my_secret_string'
60
+ uri_prefix = '/downloads'
61
+ relative_path = '/files/secret_document.pdf'
62
+ timestamp = 1.minute.from_now.to_i
63
+ token = Digest::SHA1.hexdigest(secret_string + relative_path + timestamp)
64
+ uri = "#{uri_prefix}/?token=#{token}&relative-path=#{relative_path}&timestamp=#{timestamp}"
65
+
66
+ 3) Start mongel by passing in the location of the configuration script from step 1 with the -S command
67
+ line switch
68
+
69
+ example:
70
+
71
+ mongrel_rails start -S config/secure_download_config.rb
72
+
73
+
74
+ Error messages
75
+
76
+ If any of the parameters in the URI or the secret_string are missing
77
+ the handler returns a 500 Application Error.
78
+
79
+ If the token passed in as a parameter does not match the token generated
80
+ by the handler (if someone tries to guess the token) the handler returns
81
+ a 403 Forbidden error.
82
+
83
+ If the timestamp is earlier than the current server time, meaning that the file is
84
+ no longer a valid download then the handler returns a 408 Request Time-out Error.
85
+ This error is not technically correct but it makes the most sense in the context of
86
+ the handler.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'tools/rakehelp'
7
+ require 'fileutils'
8
+ include FileUtils
9
+
10
+ setup_tests
11
+ setup_clean ["pkg", "lib/*.bundle", "*.gem", ".config"]
12
+
13
+ setup_rdoc ['README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc']
14
+
15
+ desc "Does a full compile, test run"
16
+ task :default => [:test, :package]
17
+
18
+ version="0.1"
19
+ name="mongrel_secure_download"
20
+
21
+ setup_gem(name, version) do |spec|
22
+ spec.summary = "Mongrel Secure Download Plugin"
23
+ spec.description = spec.summary
24
+ spec.author="Josh Ferguson"
25
+ spec.add_dependency('gem_plugin', '>= 0.2.1')
26
+ spec.add_dependency('mongrel', '>= 0.3.13')
27
+ spec.files += Dir.glob("resources/**/*")
28
+ end
29
+
30
+
31
+ task :install => [:test, :package] do
32
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
33
+ end
34
+
35
+ task :uninstall => [:clean] do
36
+ sh %{sudo gem uninstall #{name}}
37
+ end
38
+
@@ -0,0 +1,146 @@
1
+ require 'gem_plugin'
2
+ require 'mongrel'
3
+ require 'digest/sha1'
4
+
5
+ # = Mongrel Secure Download Handler
6
+ #
7
+ # The need to send secured files in a fast and reliable way is common.
8
+ #
9
+ # Sending a file from inside of a web application can be slow
10
+ # and also utilizes an entire application thread/process until the user
11
+ # is done downloading the file. For large files this is inefficient.
12
+ # The other option is to have the web server itself send the files as normal
13
+ # static content. This is faster but means that the files have to be in a web
14
+ # accessible public directory so that if someone guessed the URI of a file
15
+ # they could gain access to it. This is not a reasonable solution for
16
+ # situations where files need to be secured against unauthorized downloading.
17
+ #
18
+ # This handler addresses the problem of having a fast and secure
19
+ # download mechanism for web applications. The mechanism works by having
20
+ # the application generate a special URI containing a token that is only
21
+ # valid for a certain period of time. The server then recognizes this URI
22
+ # and generates a token using the parameters passed in and checks for a match
23
+ # before sending the file to the user. The key to the process is the secret
24
+ # string that both the server and the application are aware of.
25
+ #
26
+ # == URI Format
27
+ #
28
+ # A properly formed URI will have a path and query of the following format
29
+ #
30
+ # <uri-prefix>/?token=<token>&relative-path=<relative-path>&timestamp=<timestamp>
31
+ #
32
+ # === Where
33
+ #
34
+ # <uri-prefix> is a directory that does not exist in the directory structure of the application but does
35
+ # exist in the directory structure of the server.
36
+ #
37
+ # <b>example uri-prefix</b>
38
+ #
39
+ # /downloads
40
+ #
41
+ # <relative-path> is the path to the file being requested, relative to the path
42
+ # at which the web server is running. The web server must have permissions to read the file.
43
+ #
44
+ # <b>example relative-path</b>
45
+ #
46
+ # /files/your_secured_file.txt
47
+ #
48
+ # <timestamp> is the number of seconds since epoch until the time when this download expires
49
+ #
50
+ # <b>example timestamp (ruby on rails)</b>
51
+ #
52
+ # 1.minute.from_now.to_i
53
+ #
54
+ # <token> is the SHA1 hash of the concatenation of the following items:
55
+ # 1. the user defined secret string
56
+ # 2. the relative path to the file
57
+ # 3. the timestamp
58
+ #
59
+ # == Using the Handler
60
+ #
61
+ # To use the handler you need to do the following:
62
+ #
63
+ # Setup the handler within a configuration script and pass in the secret string.
64
+ #
65
+ # <b>example configuration script</b>
66
+ #
67
+ # uri "/downloads", :handler => plugin('/handlers/securedownload',{:secret_string => "my_secret_string"})
68
+ #
69
+ # In your application form a secured URI by creating the proper parameters and
70
+ # perform an SHA1 hash of the parameters to create the proper token
71
+ #
72
+ # <b>example code (ruby on rails)</b>
73
+ #
74
+ # require 'digest/sha1'
75
+ #
76
+ # secret_string = 'my_secret_string'
77
+ # uri_prefix = '/downloads'
78
+ # relative_path = '/files/secret_document.pdf'
79
+ # timestamp = 1.minute.from_now.to_i
80
+ # token = Digest::SHA1.hexdigest(secret_string + relative_path + timestamp)
81
+ # uri = "#{uri_prefix}/?token=#{token}&relative-path=#{relative_path}&timestamp=#{timestamp}"
82
+ #
83
+ # Start mongel by passing in the location of the configuration script from step 1 with the -S command
84
+ # line switch.
85
+ #
86
+ # <b>example mongrel start command</b>
87
+ #
88
+ # mongrel_rails start -S config/secure_download_config.rb
89
+ #
90
+ # == Error messages
91
+ #
92
+ # If any of the parameters in the URI or the secret_string are missing
93
+ # the handler returns a 500 Application Error.
94
+ #
95
+ # If the token passed in as a parameter does not match the token generated
96
+ # by the handler (if someone tries to guess the token) the handler returns
97
+ # a 403 Forbidden error.
98
+ #
99
+ # If the timestamp is earlier than the current server time, meaning that the file is
100
+ # no longer a valid download then the handler returns a 408 Request Time-out Error.
101
+ # This error is not technically correct but it makes the most sense in the context of
102
+ # the handler.
103
+ class SecureDownload < GemPlugin::Plugin "/handlers"
104
+ include Mongrel::HttpHandlerPlugin
105
+
106
+ def process(request, response)
107
+ query = Mongrel::HttpRequest.query_parse(request.params['QUERY_STRING'])
108
+
109
+ if @options[:secret_string].nil? or query['token'].nil? or query['timestamp'].nil? or query['relative-path'].nil?
110
+ response.start(500){}
111
+ elsif query['timestamp'].to_i < Time.now.to_i
112
+ response.start(408){}
113
+ elsif query['token'] == Digest::SHA1.hexdigest("#{@options[:secret_string]}#{query['relative-path']}#{query['timestamp']}").to_s
114
+ send_file(File.expand_path("." + query['relative-path']), response)
115
+ else
116
+ response.start(403){}
117
+ end
118
+ end
119
+
120
+ private
121
+ # Sends the contents of a file back to the user.
122
+ def send_file(path, response)
123
+ # first we setup the headers and status then we do a very fast send on the socket directly
124
+ file_status = File.stat(path)
125
+
126
+ response.status = 200
127
+ # Set the last modified times as well and etag for all files
128
+ response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate
129
+ # Calculated the same as apache, not sure how well the works on win32
130
+ response.header[Mongrel::Const::ETAG] = Mongrel::Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino]
131
+ #set the content type to something generic for now
132
+ response.header[Mongrel::Const::CONTENT_TYPE] = @default_content_type
133
+ #set the content disposition and filename
134
+ response.header['Content-Disposition'] = "attachment; filename=\"#{File.basename(path)}\""
135
+
136
+ # send a status with out content length
137
+ response.send_status(file_status.size)
138
+ response.send_header
139
+
140
+ if not header_only
141
+ response.send_file(path)
142
+ else
143
+ response.send_body # should send nothing
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,2 @@
1
+ ---
2
+ :debug: false
data/tools/rakehelp.rb ADDED
@@ -0,0 +1,105 @@
1
+
2
+ def make(makedir)
3
+ Dir.chdir(makedir) do
4
+ sh(PLATFORM =~ /win32/ ? 'nmake' : 'make')
5
+ end
6
+ end
7
+
8
+
9
+ def extconf(dir)
10
+ Dir.chdir(dir) do ruby "extconf.rb" end
11
+ end
12
+
13
+
14
+ def setup_tests
15
+ Rake::TestTask.new do |t|
16
+ t.libs << "test"
17
+ t.test_files = FileList['test/test*.rb']
18
+ t.verbose = true
19
+ end
20
+ end
21
+
22
+
23
+ def setup_clean otherfiles
24
+ files = ['build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log'] + otherfiles
25
+ CLEAN.include(files)
26
+ end
27
+
28
+
29
+ def setup_rdoc files
30
+ Rake::RDocTask.new do |rdoc|
31
+ rdoc.rdoc_dir = 'doc/rdoc'
32
+ rdoc.options << '--line-numbers'
33
+ rdoc.rdoc_files.add(files)
34
+ end
35
+ end
36
+
37
+
38
+ def setup_extension(dir, extension)
39
+ ext = "ext/#{dir}"
40
+ ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
41
+ ext_files = FileList[
42
+ "#{ext}/*.c",
43
+ "#{ext}/*.h",
44
+ "#{ext}/extconf.rb",
45
+ "#{ext}/Makefile",
46
+ "lib"
47
+ ]
48
+
49
+ task "lib" do
50
+ directory "lib"
51
+ end
52
+
53
+ desc "Builds just the #{extension} extension"
54
+ task extension.to_sym => ["#{ext}/Makefile", ext_so ]
55
+
56
+ file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
57
+ extconf "#{ext}"
58
+ end
59
+
60
+ file ext_so => ext_files do
61
+ make "#{ext}"
62
+ cp ext_so, "lib"
63
+ end
64
+ end
65
+
66
+
67
+ def base_gem_spec(pkg_name, pkg_version)
68
+ pkg_version = pkg_version
69
+ pkg_name = pkg_name
70
+ pkg_file_name = "#{pkg_name}-#{pkg_version}"
71
+ Gem::Specification.new do |s|
72
+ s.name = pkg_name
73
+ s.version = pkg_version
74
+ s.platform = Gem::Platform::RUBY
75
+ s.has_rdoc = true
76
+ s.extra_rdoc_files = [ "README" ]
77
+
78
+ s.files = %w(COPYING LICENSE README Rakefile) +
79
+ Dir.glob("{bin,doc/rdoc,test,lib}/**/*") +
80
+ Dir.glob("ext/**/*.{h,c,rb}") +
81
+ Dir.glob("examples/**/*.rb") +
82
+ Dir.glob("tools/*.rb")
83
+
84
+ s.require_path = "lib"
85
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
86
+ s.bindir = "bin"
87
+ end
88
+ end
89
+
90
+ def setup_gem(pkg_name, pkg_version)
91
+ spec = base_gem_spec(pkg_name, pkg_version)
92
+ yield spec if block_given?
93
+
94
+ Rake::GemPackageTask.new(spec) do |p|
95
+ p.gem_spec = spec
96
+ p.need_tar = true
97
+ end
98
+ end
99
+
100
+ def setup_win32_gem(pkg_name, pkg_version)
101
+ spec = base_gem_spec(pkg_name, pkg_version)
102
+ yield spec if block_given?
103
+
104
+ Gem::Builder.new(spec).build
105
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: mongrel_secure_download
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2006-05-30 00:00:00 -04:00
8
+ summary: Mongrel Secure Download Plugin
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage:
13
+ rubyforge_project:
14
+ description: Mongrel Secure Download Plugin
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Josh Ferguson
30
+ files:
31
+ - COPYING
32
+ - LICENSE
33
+ - README
34
+ - Rakefile
35
+ - lib/mongrel_secure_download
36
+ - lib/mongrel_secure_download/init.rb
37
+ - tools/rakehelp.rb
38
+ - resources/defaults.yaml
39
+ test_files: []
40
+
41
+ rdoc_options: []
42
+
43
+ extra_rdoc_files:
44
+ - README
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ requirements: []
50
+
51
+ dependencies:
52
+ - !ruby/object:Gem::Dependency
53
+ name: gem_plugin
54
+ version_requirement:
55
+ version_requirements: !ruby/object:Gem::Version::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 0.2.1
60
+ version:
61
+ - !ruby/object:Gem::Dependency
62
+ name: mongrel
63
+ version_requirement:
64
+ version_requirements: !ruby/object:Gem::Version::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.13
69
+ version: