rack_global_session 0.2

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/.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