rack-tamperproof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Joshua Hull
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,11 @@
1
+ = Rack::Tamperproof
2
+
3
+ == Usage
4
+
5
+ use Rack::Tamperproof(:default_key => "123u9ew90uqn") do
6
+ delete_for :just_delete_this
7
+ exception_for :don_t_tamper
8
+ end
9
+
10
+ # delete_for cookies will delete if they are tampered with and your request passes through normally
11
+ # exception_for produces an exception if the cookie is tampered with
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rack-tamperproof"
8
+ gem.summary = "Rack middleware that provides tamper-proof cookies"
9
+ gem.email = "joshbuddy@gmail.com"
10
+ gem.homepage = "http://github.com/joshbuddy/rack-tamperproof"
11
+ gem.authors = ["Joshua Hull"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'spec/rake/spectask'
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+
33
+ task :default => :spec
34
+
35
+ require 'rake/rdoctask'
36
+ Rake::RDocTask.new do |rdoc|
37
+ if File.exist?('VERSION.yml')
38
+ config = YAML.load(File.read('VERSION.yml'))
39
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "rack-tamperproof #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,107 @@
1
+ require 'rack'
2
+ require 'digest/sha1'
3
+
4
+ module Rack
5
+ class Request
6
+ def delete_cookie(key)
7
+ c = cookies
8
+ c.delete(key)
9
+ env['HTTP_COOKIE'] = c.map{|k,v| "#{Rack::Utils.escape(k)}=#{Rack::Utils.escape(v)}"}.join(';')
10
+ env.delete('rack.request.cookie_string')
11
+ env.delete('rack.request.cookie_hash')
12
+ end
13
+ end
14
+ end
15
+
16
+ module Rack
17
+ class Tamperproof
18
+
19
+ Tampered = Class.new(RuntimeError)
20
+
21
+ class Protector
22
+
23
+ def initialize(name, secret, postfix = '_key')
24
+ @name = name.to_s
25
+ @secret = secret
26
+ @postfix = postfix
27
+ end
28
+
29
+ def secret_cookie_key
30
+ "#{@name}#{@postfix}"
31
+ end
32
+
33
+ def self.sign(name, value, secret)
34
+ digest = Digest::SHA1.new
35
+ digest.update(name)
36
+ digest.update(value)
37
+ digest.update(secret)
38
+ digest.hexdigest
39
+ end
40
+
41
+ def secret(cookies)
42
+ self.class.sign(@name, cookies[@name], @secret)
43
+ end
44
+
45
+ def valid?(request)
46
+ secret(request.cookies) == request.cookies[secret_cookie_key]
47
+ end
48
+
49
+ def add_secret_to_response(response, cookies)
50
+ response.set_cookie(secret_cookie_key, secret(cookies))
51
+ end
52
+
53
+ class ExceptionProtector < Protector
54
+ def validate(request)
55
+ valid?(request) or raise(Tampered.new)
56
+ end
57
+ end
58
+
59
+ class DeleteProtector < Protector
60
+ def validate(request)
61
+ unless valid?(request)
62
+ request.delete_cookie(@name)
63
+ request.delete_cookie(secret_cookie_key)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def exception_for(name, key = @default_key || raise)
70
+ @protected_cookies[name.to_s] = Protector::ExceptionProtector.new(name, key)
71
+ end
72
+
73
+ def delete_for(name, key = @default_key || raise)
74
+ @protected_cookies[name.to_s] = Protector::DeleteProtector.new(name, key)
75
+ end
76
+
77
+
78
+ def initialize(app, opts = nil, &block)
79
+ @app = app
80
+ @default_key = opts && opts[:default_key]
81
+ @protected_cookies = {}
82
+ instance_eval(&block)
83
+ end
84
+
85
+ def call(env)
86
+
87
+ # detect cookies that are supposed to be tamper proof
88
+
89
+ request = Rack::Request.new(env)
90
+ request.cookies.each do |name, value|
91
+ if @protected_cookies[name]
92
+ @protected_cookies[name].validate(request)
93
+ end
94
+ end
95
+
96
+ result = @app.call(env)
97
+ response = Rack::Response.new(result[2], result[0], result[1])
98
+
99
+ cookies = Rack::Utils.parse_query(response['Set-Cookie'], "\n")
100
+ @protected_cookies.each do |name, protector|
101
+ protector.add_secret_to_response(response, cookies)
102
+ end
103
+ response.to_a
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rack-Tamperproof" do
4
+
5
+ describe "key signing" do
6
+ it "should use all three values to create a key" do
7
+ vals = ['value0','value1','value2']
8
+ 3.times do |i|
9
+ Rack::Tamperproof::Protector.sign(*vals).should_not == Rack::Tamperproof::Protector.sign(*vals.map{|v| v[-1] == i.to_s[0] ? v.upcase : v})
10
+ end
11
+ end
12
+ end
13
+
14
+ describe "with a delete cookie" do
15
+
16
+ it "should generate a secret" do
17
+ response = build_builder(Proc.new{
18
+ delete_for :test
19
+ }).call(Rack::MockRequest.env_for('/'))
20
+ headers = Rack::Utils::HeaderHash.new(response[1])
21
+ cookies = Rack::Utils.parse_query(headers['Set-Cookie'], "\n")
22
+ cookies['test'].should == 'protected'
23
+ cookies['test_key'].should == Rack::Tamperproof::Protector.sign('test', 'protected', 'testing this key')
24
+ end
25
+
26
+ it "should delete a tampered cookie" do
27
+ response = build_builder(Proc.new{
28
+ delete_for :test
29
+ }) { |request, response|
30
+ request.cookies['test'].should == nil
31
+ }.call(Rack::MockRequest.env_for('/', {
32
+ 'HTTP_COOKIE' => build_cookie_header({'test' => 'protected2', 'test_key' => Rack::Tamperproof::Protector.sign('test', 'protected', 'testing this key')})
33
+ }))
34
+
35
+ end
36
+
37
+ it "should allow override of the secret" do
38
+ response = build_builder(Proc.new{
39
+ delete_for :test, 'my new secret'
40
+ }).call(Rack::MockRequest.env_for('/'))
41
+ headers = Rack::Utils::HeaderHash.new(response[1])
42
+ cookies = Rack::Utils.parse_query(headers['Set-Cookie'], "\n")
43
+ cookies['test'].should == 'protected'
44
+ cookies['test_key'].should == Rack::Tamperproof::Protector.sign('test', 'protected', 'my new secret')
45
+ end
46
+
47
+ it "should raise if no default is given" do
48
+ Proc.new {
49
+ build_builder(Proc.new{
50
+ delete_for :test
51
+ }, nil) { |request, response|
52
+ request.cookies['test'].should == nil
53
+ }.call(Rack::MockRequest.env_for('/', {
54
+ 'HTTP_COOKIE' => build_cookie_header({'test' => 'protected2', 'test_key' => Rack::Tamperproof::Protector.sign('test', 'protected', 'testing this key')})
55
+ }))
56
+ }.should raise_error
57
+ end
58
+ end
59
+
60
+ describe "with an exception cookie" do
61
+ it "should generate a secret" do
62
+ response = build_builder(Proc.new{
63
+ exception_for :test
64
+ }).call(Rack::MockRequest.env_for('/'))
65
+ headers = Rack::Utils::HeaderHash.new(response[1])
66
+ cookies = Rack::Utils.parse_query(headers['Set-Cookie'], "\n")
67
+ cookies['test'].should == 'protected'
68
+ cookies['test_key'].should == Rack::Tamperproof::Protector.sign('test', 'protected', 'testing this key')
69
+ end
70
+
71
+ it "should raise an exception on a tampered cookie" do
72
+ Proc.new {build_builder(Proc.new{
73
+ exception_for :test
74
+ }).call(Rack::MockRequest.env_for('/', {
75
+ 'HTTP_COOKIE' => build_cookie_header({'test' => 'protected2', 'test_key' => Rack::Tamperproof::Protector.sign('test', 'protected', 'testing this key')})
76
+ }))}.should raise_error Rack::Tamperproof::Tampered
77
+
78
+ end
79
+
80
+ it "should allow override of the secret" do
81
+ response = build_builder(Proc.new{
82
+ exception_for :test, 'my new secret'
83
+ }).call(Rack::MockRequest.env_for('/'))
84
+ headers = Rack::Utils::HeaderHash.new(response[1])
85
+ cookies = Rack::Utils.parse_query(headers['Set-Cookie'], "\n")
86
+ cookies['test'].should == 'protected'
87
+ cookies['test_key'].should == Rack::Tamperproof::Protector.sign('test', 'protected', 'my new secret')
88
+ end
89
+
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec'
2
+ require 'rubygems'
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'rack-tamperproof'
6
+
7
+ def build_builder(config, key = "testing this key", &block)
8
+ app = Rack::Builder.new do
9
+ if key
10
+ use Rack::Tamperproof, :default_key => "testing this key", &config
11
+ else
12
+ use Rack::Tamperproof, &config
13
+ end
14
+ run build_app(& (block || Proc.new{ |a,b|
15
+ }))
16
+ end
17
+
18
+ end
19
+
20
+ def build_app(&block)
21
+ Proc.new { |env|
22
+ request = Rack::Request.new(env)
23
+ Rack::Response.new do |response|
24
+ response.body = ['hello']
25
+ response.header['Content-type'] = 'text/plain'
26
+ unless request.cookies['test']
27
+ response.set_cookie('test', 'protected')
28
+ end
29
+ block.call(request, response)
30
+ end.to_a
31
+ }
32
+ end
33
+
34
+ def build_cookie_header(cookies)
35
+ cookies.map{|k,v| "#{Rack::Utils.escape(k)}=#{Rack::Utils.escape(v)}"}.join(';')
36
+ end
37
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-tamperproof
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Hull
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-03 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: joshbuddy@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION
32
+ - lib/rack-tamperproof.rb
33
+ - spec/rack-tamperproof_spec.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/joshbuddy/rack-tamperproof
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Rack middleware that provides tamper-proof cookies
63
+ test_files:
64
+ - spec/rack-tamperproof_spec.rb
65
+ - spec/spec_helper.rb