rack_global_session 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .idea
2
+ pkg
3
+ tags
4
+ .emacs-project
5
+ config/environment.rb
6
+ fulllib
7
+ TAGS
8
+ doc
9
+ /config/mirror.yaml
10
+ .rvmrc
11
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack_global_session.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack_global_session (0.2)
5
+ activesupport (~> 3.0.3)
6
+ has_global_session (~> 1.1.3)
7
+ i18n (~> 0.5.0)
8
+ rack (~> 1.2)
9
+ rack-contrib (~> 1.0.1)
10
+ tzinfo
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ activesupport (3.0.3)
16
+ flexmock (0.8.11)
17
+ has_global_session (1.1.3)
18
+ activesupport (>= 2.1.2)
19
+ json (>= 1.1.7)
20
+ uuidtools (>= 1.0.7)
21
+ i18n (0.5.0)
22
+ json (1.4.6)
23
+ rack (1.2.1)
24
+ rack-contrib (1.0.1)
25
+ rack (>= 0.9.1)
26
+ rspec (1.3.0)
27
+ rtags (0.97)
28
+ tzinfo (0.3.23)
29
+ uuidtools (2.1.1)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ activesupport (~> 3.0.3)
36
+ flexmock (~> 0.8.11)
37
+ has_global_session (~> 1.1.3)
38
+ i18n (~> 0.5.0)
39
+ rack (~> 1.2)
40
+ rack-contrib (~> 1.0.1)
41
+ rack_global_session!
42
+ rspec (~> 1.3)
43
+ rtags (~> 0.97)
44
+ tzinfo
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 RightScale, Inc.
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 NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
1
+ = rack_global_session
2
+
3
+ == DESCRIPTION
4
+
5
+ === Synopsis
6
+
7
+ rack_global_session is a shim to make has_global_session work as Rack
8
+ middleware. rack_global_session requires that Rack::Cookies from
9
+ rack-contrib be loaded before it.
10
+
11
+ == USAGE
12
+
13
+ === Simple Example
14
+
15
+ use Rack::Cookies
16
+ use Rack::GlobalSession, filename
17
+
18
+ === Complex Example
19
+
20
+ use Rack::Cookies
21
+ use Rack::GlobalSession, filename, do |env|
22
+ auth = Rack::Auth::Basic::Request.new(env)
23
+ auth.provided? && auth.basic? && auth.credentials[1]
24
+ end
25
+
26
+ == INSTALLATION
27
+
28
+ This gem can be installed by entering the following at a command
29
+ prompt:
30
+
31
+ gem install rack_global_session
32
+
33
+ == TESTING
34
+
35
+ Install the following gems for testing:
36
+ - rspec
37
+ - flexmock
38
+
39
+ Then the build can be tested with
40
+
41
+ rake spec
42
+
43
+ == LICENSE
44
+
45
+ <b>RightScraper</b>
46
+
47
+ Copyright:: Copyright (c) 2010 RightScale, Inc.
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,93 @@
1
+ #-- -*-ruby-*-
2
+ # Copyright: Copyright (c) 2010 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'rubygems'
25
+ require 'bundler'
26
+ require 'fileutils'
27
+ require 'rake'
28
+ require 'spec/rake/spectask'
29
+ require 'rake/rdoctask'
30
+ require 'rake/gempackagetask'
31
+ require 'rake/clean'
32
+
33
+ Bundler::GemHelper.install_tasks
34
+
35
+ # == Gem == #
36
+
37
+ gemtask = Rake::GemPackageTask.new(Gem::Specification.load("rack_global_session.gemspec")) do |package|
38
+ package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
39
+ package.need_zip = true
40
+ package.need_tar = true
41
+ end
42
+
43
+ directory gemtask.package_dir
44
+
45
+ CLEAN.include(gemtask.package_dir)
46
+
47
+ # == Unit Tests == #
48
+
49
+ task :specs => :spec
50
+
51
+ desc "Run unit tests"
52
+ Spec::Rake::SpecTask.new do |t|
53
+ t.spec_files = Dir['spec/**/*_spec.rb']
54
+ t.spec_opts = lambda do
55
+ IO.readlines(File.join(File.dirname(__FILE__), 'spec', 'spec.opts')).map {|l| l.chomp.split " "}.flatten
56
+ end
57
+ end
58
+
59
+ desc "Run unit tests with RCov"
60
+ Spec::Rake::SpecTask.new(:rcov) do |t|
61
+ t.spec_files = Dir['spec/**/*_spec.rb']
62
+ t.rcov = true
63
+ t.rcov_opts = lambda do
64
+ IO.readlines(File.join(File.dirname(__FILE__), 'spec', 'rcov.opts')).map {|l| l.chomp.split " "}.flatten
65
+ end
66
+ end
67
+
68
+ desc "Print Specdoc for unit tests"
69
+ Spec::Rake::SpecTask.new(:doc) do |t|
70
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
71
+ t.spec_files = Dir['spec/**/*_spec.rb']
72
+ end
73
+
74
+ # == Documentation == #
75
+
76
+ desc "Generate API documentation to doc/rdocs/index.html"
77
+ Rake::RDocTask.new do |rd|
78
+ rd.rdoc_dir = 'doc/rdocs'
79
+ rd.main = 'README.rdoc'
80
+ rd.rdoc_files.include 'README.rdoc', "lib/**/*.rb"
81
+
82
+ rd.options << '--inline-source'
83
+ rd.options << '--line-numbers'
84
+ rd.options << '--all'
85
+ rd.options << '--fileboxes'
86
+ rd.options << '--diagram'
87
+ end
88
+
89
+ # == Emacs integration == #
90
+ desc "Rebuild TAGS file"
91
+ task :tags do
92
+ sh "rtags -R lib spec"
93
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module GlobalSessions
3
+ VERSION = "0.2"
4
+ end
5
+ end
@@ -0,0 +1,154 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require "has_global_session"
25
+ require "active_support"
26
+ require "active_support/time"
27
+
28
+ module Rack
29
+ # A port of has_global_session to Rack middleware.
30
+ module GlobalSessions
31
+ # Alias some of the HasGlobalSession classes for easy typing.
32
+ Configuration = HasGlobalSession::Configuration
33
+ Directory = HasGlobalSession::Directory
34
+ Session = HasGlobalSession::GlobalSession
35
+ # Global session middleware. Note: this class relies on
36
+ # Rack::Cookies being used higher up in the chain.
37
+ class GlobalSession
38
+ # Make a new global session.
39
+ #
40
+ # The optional block here controls an alternate ticket retrieval
41
+ # method. If no ticket is stored in the cookie jar, this
42
+ # function is called. If it returns a non-nil value, that value
43
+ # is the ticket.
44
+ #
45
+ # === Parameters
46
+ # app(Rack client): application to run
47
+ # configuration(String or HasGlobalSession::Configuration): has_global_session configuration.
48
+ # If a string, is interpreted as a
49
+ # filename to load the config from.
50
+ # block: optional alternate ticket retrieval function
51
+ def initialize(app, configuration, &block)
52
+ @app = app
53
+ if configuration.instance_of?(String)
54
+ @configuration = Configuration.new(configuration, ENV['RACK_ENV'] || 'development')
55
+ else
56
+ @configuration = configuration
57
+ end
58
+ @cookie_retrieval = block
59
+ @directory = Directory.new(@configuration, @configuration['directory'])
60
+ @cookie_name = @configuration['cookie']['name']
61
+ end
62
+
63
+ # Read a cookie from the Rack environment.
64
+ #
65
+ # === Parameters
66
+ # env(Hash): Rack environment.
67
+ def read_cookie(env)
68
+ begin
69
+ if env['rack.cookies'].key?(@cookie_name)
70
+ env['global_session'] = Session.new(@directory,
71
+ env['rack.cookies'][@cookie_name])
72
+ elsif @cookie_retrieval && cookie = @cookie_retrieval.call(env)
73
+ env['global_session'] = Session.new(@directory, cookie)
74
+ else
75
+ env['global_session'] = Session.new(@directory)
76
+ end
77
+ true
78
+ rescue Exception => e
79
+ # Reinitialize global session cookie
80
+ env['global_session'] = Session.new(@directory)
81
+ update_cookie(env)
82
+ raise e
83
+ end
84
+ end
85
+
86
+ # Renew the session ticket.
87
+ #
88
+ # === Parameters
89
+ # env(Hash): Rack environment
90
+ def renew_ticket(env)
91
+ if @configuration['renew'] && env['global_session'] &&
92
+ env['global_session'].directory.local_authority_name &&
93
+ env['global_session'].expired_at < renew.to_i.minutes.from_now.utc
94
+ env['global_session'].renew!
95
+ end
96
+ end
97
+
98
+ # Update the cookie jar with the revised ticket.
99
+ #
100
+ # === Parameters
101
+ # env(Hash): Rack environment
102
+ def update_cookie(env)
103
+ begin
104
+ domain = @configuration['cookie']['domain'] || ENV['SERVER_NAME']
105
+ if env['global_session'] && env['global_session'].valid?
106
+ value = env['global_session'].to_s
107
+ expires = @configuration['ephemeral'] ? nil : env['global_session'].expired_at
108
+ unless env['rack.cookies'].key?(@cookie_name) &&
109
+ env['rack.cookies'][@cookie_name] == value
110
+ env['rack.cookies'][@cookie_name] = {:value => value, :domain => domain, :expires => expires}
111
+ end
112
+ else
113
+ # write an empty cookie
114
+ env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
115
+ end
116
+ rescue Exception => e
117
+ wipe_cookie(env)
118
+ raise e
119
+ end
120
+ end
121
+
122
+ # Delete the ticket from the cookie jar.
123
+ #
124
+ # === Parameters
125
+ # env(Hash): Rack environment
126
+ def wipe_cookie(env)
127
+ domain = @configuration['cookie']['domain'] || ENV['SERVER_NAME']
128
+ env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
129
+ end
130
+
131
+ # Rack request chain. Sets up the global session ticket from
132
+ # the environment and passes it up the chain.
133
+ def call(env)
134
+ env['rack.cookies'] = {} unless env['rack.cookies']
135
+ begin
136
+ read_cookie(env)
137
+ renew_ticket(env)
138
+ tuple = @app.call(env)
139
+ rescue Exception => e
140
+ wipe_cookie(env)
141
+ $stderr.puts "Error while reading cookies: #{e.class} #{e} #{e.backtrace}"
142
+ if env['rack.logger']
143
+ env['rack.logger'].error("Error while reading cookies: #{e.class} #{e} #{e.backtrace}")
144
+ end
145
+ return [503, {'Content-Type' => 'text/plain'}, "Invalid cookie"]
146
+ else
147
+ update_cookie(env)
148
+ return tuple
149
+ end
150
+ end
151
+ end
152
+ end
153
+ GlobalSession = GlobalSessions::GlobalSession
154
+ end
@@ -0,0 +1,57 @@
1
+ # -*-mode: ruby-mode; encoding: utf-8-*-
2
+ # Copyright: Copyright (c) 2010 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ $:.push File.expand_path("../lib", __FILE__)
24
+ require "rack_global_session/version"
25
+
26
+ Gem::Specification.new do |spec|
27
+ spec.name = "rack_global_session"
28
+ spec.version = Rack::GlobalSessions::VERSION
29
+ spec.summary = "Add global session handling to Rack servers"
30
+ spec.description = <<EOS
31
+ A port of has_global_session to Rack middleware.
32
+ EOS
33
+ spec.authors = ['Graham Hughes']
34
+ spec.email = 'graham@rightscale.com'
35
+ spec.platform = Gem::Platform::RUBY
36
+ spec.has_rdoc = true
37
+ spec.rdoc_options = ["--main", "README.rdoc", "--title", "Rack::GlobalSession"]
38
+ spec.extra_rdoc_files = ["README.rdoc"]
39
+ spec.required_ruby_version = '>= 1.8.7'
40
+ spec.require_path = 'lib'
41
+
42
+ spec.add_dependency 'activesupport', '~> 3.0.3'
43
+ spec.add_dependency 'i18n', "~> 0.5.0"
44
+ spec.add_dependency 'tzinfo'
45
+ spec.add_dependency 'has_global_session', '~> 1.1.3'
46
+ spec.add_dependency 'rack', '~> 1.2'
47
+ spec.add_dependency 'rack-contrib', '~> 1.0.1'
48
+
49
+ spec.add_development_dependency 'rspec', "~> 1.3"
50
+ spec.add_development_dependency 'flexmock', "~> 0.8.11"
51
+ spec.add_development_dependency 'rtags', "~> 0.97"
52
+
53
+ spec.files = `git ls-files`.split("\n")
54
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
55
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
56
+ spec.require_paths = ["lib"]
57
+ end
@@ -0,0 +1,200 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
25
+ require 'tmpdir'
26
+ require 'fileutils'
27
+ require 'stringio'
28
+
29
+ module Rack
30
+ describe GlobalSession do
31
+ it_should_behave_like 'Authentication'
32
+
33
+ before(:each) do
34
+ ENV['SERVER_NAME'] = "server"
35
+ ENV['RACK_ENV'] = "test"
36
+ end
37
+
38
+ before(:each) do
39
+ @olderr = $stderr
40
+ $stderr = StringIO.new
41
+ end
42
+
43
+ after(:each) do
44
+ $stderr = @olderr
45
+ end
46
+
47
+ it 'should create an empty session if none exists' do
48
+ token = Object.new
49
+ @app = Proc.new do |env|
50
+ env['global_session'].should_not be_nil
51
+ env['global_session'].should be_valid
52
+ token
53
+ end
54
+ environment = {}
55
+ Rack::GlobalSession.new(@app, @configuration).call(environment).should == token
56
+ environment['rack.cookies']["aCookie"][:value].should_not be_nil
57
+ environment['rack.cookies']["aCookie"][:domain].should == "example.com"
58
+ environment['rack.cookies']["aCookie"][:expires].should <= 10.minutes.from_now.utc
59
+ environment['rack.cookies']["aCookie"][:expires].should >= 9.minutes.from_now.utc
60
+ end
61
+ it 'should use SERVER_NAME if no domain is specified' do
62
+ @config_hash["common"]["cookie"].delete("domain")
63
+ dump_config(@config_hash)
64
+ environment = {}
65
+ Rack::GlobalSession.new(lambda {}, @configuration).call(environment)
66
+ environment['rack.cookies']["aCookie"][:domain].should == "server"
67
+ end
68
+
69
+ it 'should raise an error on ludicrously invalid cookies' do
70
+ environment = {"rack.cookies" => {
71
+ "aCookie" => "foo"
72
+ }}
73
+ key, hash, value = Rack::GlobalSession.new(lambda {}, @configuration).call(environment)
74
+ key.should == 503
75
+ hash.should == {'Content-Type' => 'text/plain'}
76
+ value.should == "Invalid cookie"
77
+ $stderr.string.should =~ /HasGlobalSession::MalformedCookie/
78
+ $stderr.string.should =~ /buffer error/
79
+ end
80
+
81
+ it 'should raise an error on well formed but invalid cookies' do
82
+ hash = {'id' => "root",
83
+ 'tc' => Time.now, 'te' => Time.now,
84
+ 'ds' => "not actually a signature",
85
+ 'dx' => {"third" => 4},
86
+ 's' => @first_key.private_encrypt("blah"),
87
+ 'a' => "first",
88
+ }
89
+ json = HasGlobalSession::Encoding::JSON.dump(hash)
90
+ compressed = Zlib::Deflate.deflate(json, Zlib::BEST_COMPRESSION)
91
+ base64 = HasGlobalSession::Encoding::Base64Cookie.dump(compressed)
92
+ environment = {"rack.cookies" => {"aCookie" => base64}}
93
+ key, hash, value = Rack::GlobalSession.new(lambda {}, @configuration).call(environment)
94
+ key.should == 503
95
+ hash.should == {'Content-Type' => 'text/plain'}
96
+ value.should == "Invalid cookie"
97
+ $stderr.string.should =~ /OpenSSL::PKey::RSAError/
98
+ end
99
+
100
+ context 'with a valid environment' do
101
+ before(:each) do
102
+ @environment = {}
103
+ Rack::GlobalSession.new(lambda {}, @configuration).call(@environment)
104
+ @environment['rack.cookies']['aCookie'] = @environment['rack.cookies']['aCookie'][:value]
105
+ end
106
+
107
+ it 'should be able to read cookies from random places' do
108
+ Rack::GlobalSession.new(lambda {}, @configuration) do |env|
109
+ env['foo']
110
+ end.call({'foo' => @environment['rack.cookies']['aCookie']})
111
+ end
112
+
113
+ it 'should read a valid cookie' do
114
+ Rack::GlobalSession.new(lambda {}, @configuration).call(@environment)
115
+ end
116
+
117
+ context 'with an expired session' do
118
+ before(:each) do
119
+ Rack::GlobalSession.new(lambda {|e|
120
+ e['global_session'].instance_variable_set(:@expired_at,
121
+ 1.minutes.from_now)},
122
+ @configuration).call(@environment)
123
+ @environment['rack.cookies']['aCookie'] = @environment['rack.cookies']['aCookie'][:value]
124
+ end
125
+
126
+ it 'should renew expired cookies when permitted' do
127
+ Rack::GlobalSession.new(lambda {|e| e['global_session'].renew!},
128
+ @configuration).call(@environment)
129
+ @environment['rack.cookies']["aCookie"][:expires].should <= 10.minutes.from_now.utc
130
+ @environment['rack.cookies']["aCookie"][:expires].should >= 9.minutes.from_now.utc
131
+ end
132
+
133
+ it 'should renew expired cookies implicitly' do
134
+ Rack::GlobalSession.new(lambda {|e| e['global_session'].expired_at.should >=
135
+ 9.minutes.from_now.utc},
136
+ @configuration).call(@environment)
137
+ end
138
+ end
139
+
140
+ context 'when it is not an authority' do
141
+ before(:each) do
142
+ @config_hash["common"].delete("authority")
143
+ dump_config(@config_hash)
144
+ end
145
+
146
+ it 'should not renew expired cookies' do
147
+ key, hash, value = Rack::GlobalSession.new(lambda {|e| e['global_session'].renew!},
148
+ @configuration).call(@environment)
149
+ key.should == 503
150
+ hash.should == {'Content-Type' => 'text/plain'}
151
+ value.should == "Invalid cookie"
152
+ $stderr.string.should =~ /HasGlobalSession::NoAuthority/
153
+ end
154
+
155
+ it 'should not attempt to update the cookie when it is not an authority' do
156
+ key, hash, value = Rack::GlobalSession.new(lambda {|e| e['global_session']['first'] = 4},
157
+ @configuration).call(@environment)
158
+ key.should == 503
159
+ hash.should == {'Content-Type' => 'text/plain'}
160
+ value.should == "Invalid cookie"
161
+ $stderr.string.should =~ /HasGlobalSession::NoAuthority/
162
+ end
163
+ end
164
+
165
+ it 'should update cookies correctly' do
166
+ Rack::GlobalSession.new(lambda { |env|
167
+ env['global_session']['first'] = "foo"
168
+ env['global_session']['second'] = "bar"
169
+ }, @configuration).call(@environment)
170
+ oldvalue = @environment['rack.cookies']['aCookie'][:value]
171
+ @environment['rack.cookies']['aCookie'] = @environment['rack.cookies']['aCookie'][:value]
172
+ Rack::GlobalSession.new(lambda { |env|
173
+ env['global_session']['first'].should == "foo"
174
+ env['global_session']['second'].should == "bar"
175
+ env['global_session']['first'] = "baz"
176
+ }, @configuration).call(@environment)
177
+ @environment['rack.cookies']['aCookie'][:value].should_not == oldvalue
178
+ end
179
+
180
+ it 'should unconditionally wipe the cookie if an error occurs' do
181
+ @environment['rack.cookies']['aCookie'].should_not be_nil
182
+ key, hash, value = Rack::GlobalSession.new(lambda {raise "foo"}, @configuration).call(@environment)
183
+ key.should == 503
184
+ hash.should == {'Content-Type' => 'text/plain'}
185
+ value.should == "Invalid cookie"
186
+ @environment['rack.cookies']['aCookie'][:value].should be_nil
187
+ end
188
+
189
+ it 'should refuse cookies from invalid certification authorities' do
190
+ @config_hash["common"]["trust"] = "second"
191
+ dump_config(@config_hash)
192
+ key, hash, value = Rack::GlobalSession.new(lambda {}, @configuration).call(@environment)
193
+ key.should == 503
194
+ hash.should == {'Content-Type' => 'text/plain'}
195
+ value.should == "Invalid cookie"
196
+ $stderr.string.should =~ /SecurityError/
197
+ end
198
+ end
199
+ end
200
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1 @@
1
+ --exclude "spec/*"~
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --format=nested
2
+ --backtrace
@@ -0,0 +1,128 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require "rubygems"
25
+ require 'bundler/setup'
26
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
27
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/rack_global_session")
28
+ require "spec"
29
+ require "flexmock"
30
+ require "tmpdir"
31
+ require "yaml"
32
+
33
+ Spec::Runner.configuration.mock_with :flexmock
34
+
35
+ module Rack::GlobalSessions
36
+ # Helper class for managing OpenSSL keys.
37
+ class KeyFactory
38
+ # Make a new keystore, including a temporary directory.
39
+ def initialize
40
+ @keystore = Dir.mktmpdir
41
+ end
42
+
43
+ # Return the directory all keys are stored in.
44
+ #
45
+ # === Returns
46
+ # keystore(String): directory where all keys are stored
47
+ def dir
48
+ @keystore
49
+ end
50
+
51
+ # Create a new OpenSSL key and write it to the temporary directory.
52
+ #
53
+ # === Parameters
54
+ # name(String): name of key
55
+ # write_private(Boolean): whether to write the private key to the directory
56
+ #
57
+ # === Returns
58
+ # new_key(OpenSSL::PKey::RSA): key generated
59
+ def create(name, write_private)
60
+ new_key = OpenSSL::PKey::RSA.generate(1024)
61
+ new_public = new_key.public_key.to_pem
62
+ new_private = new_key.to_pem
63
+ File.open(File.join(@keystore, "#{name}.pub"), 'w') { |f| f.puts new_public }
64
+ File.open(File.join(@keystore, "#{name}.key"), 'w') { |f| f.puts new_key } if write_private
65
+ new_key
66
+ end
67
+
68
+ # Remove all keys in the key store.
69
+ def reset()
70
+ Dir[File.join(@keystore, "*")].each { |f| FileUtils.remove_entry_secure f }
71
+ end
72
+
73
+ # Tear down the keystore.
74
+ def destroy()
75
+ FileUtils.remove_entry_secure(@keystore)
76
+ end
77
+ end
78
+
79
+ module SpecHelpers
80
+ shared_examples_for "Authentication" do
81
+ before(:all) do
82
+ @factory = KeyFactory.new
83
+ end
84
+
85
+ before(:each) do
86
+ @first_key = @factory.create("first", true)
87
+ @second_key = @factory.create("second", false)
88
+ @config_hash = {
89
+ "common" => {
90
+ "attributes" => {
91
+ "signed" => ["first", "second"],
92
+ "insecure" => ["third"]
93
+ },
94
+ "authority" => "first",
95
+ "trust" => "first",
96
+ "timeout" => 10,
97
+ "directory" => @factory.dir,
98
+ "cookie" => {
99
+ "name" => "aCookie",
100
+ "domain" => "example.com",
101
+ }
102
+ }
103
+ }
104
+ @config_file = File.join(@factory.dir, "config")
105
+ dump_config(@config_hash)
106
+ end
107
+
108
+ # Dump configuration for has_global_session to the config file.
109
+ #
110
+ # === Parameters
111
+ # hash(Hash): has_global_session configuration
112
+ def dump_config(hash)
113
+ File.open(@config_file, "w") do |file|
114
+ file << YAML.dump(hash)
115
+ end
116
+ @configuration = Configuration.new(@config_file, "test")
117
+ end
118
+
119
+ after(:all) do
120
+ @factory.destroy
121
+ end
122
+
123
+ after(:each) do
124
+ @factory.reset
125
+ end
126
+ end
127
+ end
128
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_global_session
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ version: "0.2"
10
+ platform: ruby
11
+ authors:
12
+ - Graham Hughes
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-30 00:00:00 -08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 1
29
+ segments:
30
+ - 3
31
+ - 0
32
+ - 3
33
+ version: 3.0.3
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 11
45
+ segments:
46
+ - 0
47
+ - 5
48
+ - 0
49
+ version: 0.5.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: tzinfo
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: has_global_session
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ hash: 21
75
+ segments:
76
+ - 1
77
+ - 1
78
+ - 3
79
+ version: 1.1.3
80
+ type: :runtime
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: rack
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ hash: 11
91
+ segments:
92
+ - 1
93
+ - 2
94
+ version: "1.2"
95
+ type: :runtime
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-contrib
99
+ prerelease: false
100
+ requirement: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ hash: 21
106
+ segments:
107
+ - 1
108
+ - 0
109
+ - 1
110
+ version: 1.0.1
111
+ type: :runtime
112
+ version_requirements: *id006
113
+ - !ruby/object:Gem::Dependency
114
+ name: rspec
115
+ prerelease: false
116
+ requirement: &id007 !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ~>
120
+ - !ruby/object:Gem::Version
121
+ hash: 9
122
+ segments:
123
+ - 1
124
+ - 3
125
+ version: "1.3"
126
+ type: :development
127
+ version_requirements: *id007
128
+ - !ruby/object:Gem::Dependency
129
+ name: flexmock
130
+ prerelease: false
131
+ requirement: &id008 !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ~>
135
+ - !ruby/object:Gem::Version
136
+ hash: 41
137
+ segments:
138
+ - 0
139
+ - 8
140
+ - 11
141
+ version: 0.8.11
142
+ type: :development
143
+ version_requirements: *id008
144
+ - !ruby/object:Gem::Dependency
145
+ name: rtags
146
+ prerelease: false
147
+ requirement: &id009 !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ hash: 201
153
+ segments:
154
+ - 0
155
+ - 97
156
+ version: "0.97"
157
+ type: :development
158
+ version_requirements: *id009
159
+ description: |
160
+ A port of has_global_session to Rack middleware.
161
+
162
+ email: graham@rightscale.com
163
+ executables: []
164
+
165
+ extensions: []
166
+
167
+ extra_rdoc_files:
168
+ - README.rdoc
169
+ files:
170
+ - .gitignore
171
+ - Gemfile
172
+ - Gemfile.lock
173
+ - LICENSE
174
+ - README.rdoc
175
+ - Rakefile
176
+ - lib/rack_global_session.rb
177
+ - lib/rack_global_session/version.rb
178
+ - rack_global_session.gemspec
179
+ - spec/rack_global_session_spec.rb
180
+ - spec/rcov.opts
181
+ - spec/spec.opts
182
+ - spec/spec_helper.rb
183
+ has_rdoc: true
184
+ homepage:
185
+ licenses: []
186
+
187
+ post_install_message:
188
+ rdoc_options:
189
+ - --main
190
+ - README.rdoc
191
+ - --title
192
+ - Rack::GlobalSession
193
+ require_paths:
194
+ - lib
195
+ required_ruby_version: !ruby/object:Gem::Requirement
196
+ none: false
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ hash: 57
201
+ segments:
202
+ - 1
203
+ - 8
204
+ - 7
205
+ version: 1.8.7
206
+ required_rubygems_version: !ruby/object:Gem::Requirement
207
+ none: false
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ hash: 3
212
+ segments:
213
+ - 0
214
+ version: "0"
215
+ requirements: []
216
+
217
+ rubyforge_project:
218
+ rubygems_version: 1.3.7
219
+ signing_key:
220
+ specification_version: 3
221
+ summary: Add global session handling to Rack servers
222
+ test_files:
223
+ - spec/rack_global_session_spec.rb
224
+ - spec/rcov.opts
225
+ - spec/spec.opts
226
+ - spec/spec_helper.rb