global_session 2.0.3 → 3.0.0

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/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
3
3
  require 'rake'
4
4
  require 'right_develop'
5
5
  require 'spec/rake/spectask'
6
- require 'rake/gempackagetask'
6
+ require 'rubygems/package_task'
7
7
  require 'rake/clean'
8
8
  require 'cucumber/rake/task'
9
9
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.3
1
+ 3.0.0
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{global_session}
8
- s.version = "2.0.3"
8
+ s.version = "3.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tony Spataro"]
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
36
36
  "lib/global_session/session/abstract.rb",
37
37
  "lib/global_session/session/v1.rb",
38
38
  "lib/global_session/session/v2.rb",
39
+ "lib/global_session/session/v3.rb",
39
40
  "rails/init.rb",
40
41
  "rails_generators/global_session/USAGE",
41
42
  "rails_generators/global_session/global_session_generator.rb",
@@ -54,51 +55,51 @@ Gem::Specification.new do |s|
54
55
  s.specification_version = 3
55
56
 
56
57
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
- s.add_runtime_dependency(%q<right_support>, ["~> 2.5"])
58
- s.add_runtime_dependency(%q<simple_uuid>, [">= 0.2.0"])
59
58
  s.add_runtime_dependency(%q<json>, ["~> 1.4"])
60
- s.add_runtime_dependency(%q<msgpack>, ["~> 0.4"])
61
59
  s.add_runtime_dependency(%q<rack-contrib>, ["~> 1.0"])
62
- s.add_development_dependency(%q<rake>, ["~> 0.8"])
63
- s.add_development_dependency(%q<rspec>, ["~> 1.3"])
60
+ s.add_runtime_dependency(%q<right_support>, [">= 2.8.1", "< 3.0"])
61
+ s.add_runtime_dependency(%q<simple_uuid>, [">= 0.2.0"])
64
62
  s.add_development_dependency(%q<cucumber>, ["~> 1.0"])
65
- s.add_development_dependency(%q<right_develop>, ["~> 1.2"])
63
+ s.add_development_dependency(%q<debugger>, ["~> 1.5"])
66
64
  s.add_development_dependency(%q<flexmock>, ["~> 0.8"])
67
- s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
68
65
  s.add_development_dependency(%q<httpclient>, [">= 0"])
66
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
67
+ s.add_development_dependency(%q<msgpack>, ["~> 0.4"])
68
+ s.add_development_dependency(%q<rake>, ["~> 0.8"])
69
+ s.add_development_dependency(%q<right_develop>, ["~> 1.2"])
70
+ s.add_development_dependency(%q<rspec>, ["~> 1.3"])
69
71
  s.add_development_dependency(%q<ruby-debug>, ["~> 0.10"])
70
- s.add_development_dependency(%q<debugger>, ["~> 1.5"])
71
72
  else
72
- s.add_dependency(%q<right_support>, ["~> 2.5"])
73
- s.add_dependency(%q<simple_uuid>, [">= 0.2.0"])
74
73
  s.add_dependency(%q<json>, ["~> 1.4"])
75
- s.add_dependency(%q<msgpack>, ["~> 0.4"])
76
74
  s.add_dependency(%q<rack-contrib>, ["~> 1.0"])
77
- s.add_dependency(%q<rake>, ["~> 0.8"])
78
- s.add_dependency(%q<rspec>, ["~> 1.3"])
75
+ s.add_dependency(%q<right_support>, [">= 2.8.1", "< 3.0"])
76
+ s.add_dependency(%q<simple_uuid>, [">= 0.2.0"])
79
77
  s.add_dependency(%q<cucumber>, ["~> 1.0"])
80
- s.add_dependency(%q<right_develop>, ["~> 1.2"])
78
+ s.add_dependency(%q<debugger>, ["~> 1.5"])
81
79
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
82
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
83
80
  s.add_dependency(%q<httpclient>, [">= 0"])
81
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
82
+ s.add_dependency(%q<msgpack>, ["~> 0.4"])
83
+ s.add_dependency(%q<rake>, ["~> 0.8"])
84
+ s.add_dependency(%q<right_develop>, ["~> 1.2"])
85
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
84
86
  s.add_dependency(%q<ruby-debug>, ["~> 0.10"])
85
- s.add_dependency(%q<debugger>, ["~> 1.5"])
86
87
  end
87
88
  else
88
- s.add_dependency(%q<right_support>, ["~> 2.5"])
89
- s.add_dependency(%q<simple_uuid>, [">= 0.2.0"])
90
89
  s.add_dependency(%q<json>, ["~> 1.4"])
91
- s.add_dependency(%q<msgpack>, ["~> 0.4"])
92
90
  s.add_dependency(%q<rack-contrib>, ["~> 1.0"])
93
- s.add_dependency(%q<rake>, ["~> 0.8"])
94
- s.add_dependency(%q<rspec>, ["~> 1.3"])
91
+ s.add_dependency(%q<right_support>, [">= 2.8.1", "< 3.0"])
92
+ s.add_dependency(%q<simple_uuid>, [">= 0.2.0"])
95
93
  s.add_dependency(%q<cucumber>, ["~> 1.0"])
96
- s.add_dependency(%q<right_develop>, ["~> 1.2"])
94
+ s.add_dependency(%q<debugger>, ["~> 1.5"])
97
95
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
98
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
99
96
  s.add_dependency(%q<httpclient>, [">= 0"])
97
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
98
+ s.add_dependency(%q<msgpack>, ["~> 0.4"])
99
+ s.add_dependency(%q<rake>, ["~> 0.8"])
100
+ s.add_dependency(%q<right_develop>, ["~> 1.2"])
101
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
100
102
  s.add_dependency(%q<ruby-debug>, ["~> 0.10"])
101
- s.add_dependency(%q<debugger>, ["~> 1.5"])
102
103
  end
103
104
  end
104
105
 
@@ -99,7 +99,6 @@ module GlobalSession
99
99
  #
100
100
  # === Parameters
101
101
  # cookie(String):: DEPRECATED - Optional, serialized global session cookie. If none is supplied, a new session is created.
102
- # valid_signature_digest(String):: DEPRECATED - Optional,
103
102
  #
104
103
  # === Return
105
104
  # session(Session):: the newly-initialized session
@@ -109,20 +108,24 @@ module GlobalSession
109
108
  # ExpiredSession:: if the session contained in the cookie has expired
110
109
  # MalformedCookie:: if the cookie was corrupt or malformed
111
110
  # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
112
- def create_session(cookie=nil, valid_signature_digest=nil)
111
+ def create_session(cookie=nil)
113
112
  forced_version = configuration['cookie']['version']
114
113
 
115
114
  if cookie.nil?
116
115
  # Create a legitimately new session
117
116
  case forced_version
117
+ when 3, nil
118
+ Session::V3.new(self, cookie)
119
+ when 2
120
+ Session::V2.new(self, cookie)
118
121
  when 1
119
- Session::V1.new(self, cookie, valid_signature_digest)
122
+ Session::V1.new(self, cookie)
120
123
  else
121
- Session.new(self, cookie, valid_signature_digest)
124
+ raise ArgumentError, "Unknown value #{forced_version} for configuration.cookie.version"
122
125
  end
123
126
  else
124
127
  warn "GlobalSession::Directory#create_session with an existing session is DEPRECATED -- use #load_session instead"
125
- load_session(cookie, valid_signature_digest)
128
+ load_session(cookie)
126
129
  end
127
130
  end
128
131
 
@@ -130,7 +133,6 @@ module GlobalSession
130
133
  #
131
134
  # === Parameters
132
135
  # cookie(String):: Optional, serialized global session cookie. If none is supplied, a new session is created.
133
- # valid_signature_digest(String):: Optional,
134
136
  #
135
137
  # === Return
136
138
  # session(Session):: the newly-initialized session
@@ -140,8 +142,8 @@ module GlobalSession
140
142
  # ExpiredSession:: if the session contained in the cookie has expired
141
143
  # MalformedCookie:: if the cookie was corrupt or malformed
142
144
  # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
143
- def load_session(cookie, valid_signature_digest=nil)
144
- Session.new(self, cookie, valid_signature_digest)
145
+ def load_session(cookie)
146
+ Session.new(self, cookie)
145
147
  end
146
148
 
147
149
  def local_authority_name
@@ -19,6 +19,15 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ # Standard library (or possibly gem) dependencies
23
+ require 'json'
24
+
25
+ begin
26
+ require 'msgpack'
27
+ rescue LoadError => e
28
+ # Msgpack is optional (only required for V2 session support)
29
+ end
30
+
22
31
  module GlobalSession
23
32
  # Various encoding (not encryption!) techniques used by the global session plugin.
24
33
  #
@@ -26,6 +35,10 @@ module GlobalSession
26
35
  # JSON serializer, used to serialize Hash objects in a form suitable
27
36
  # for stuffing into a cookie.
28
37
  #
38
+ # Note that the #load method does not have the serialization semantics of the
39
+ # Marshal/YAML load/dump interface; any embedded Ruby objects will remain in
40
+ # their hash-serialized form. (Global session contents are not trustworthy enough
41
+ # to allow Ruby object serialization.)
29
42
  module JSON
30
43
  # Unserialize JSON to Hash.
31
44
  #
@@ -35,7 +48,7 @@ module GlobalSession
35
48
  # === Return
36
49
  # value(Hash):: An unserialized Ruby Hash
37
50
  def self.load(json)
38
- ::JSON.load(json)
51
+ ::JSON.parse(json)
39
52
  end
40
53
 
41
54
  # Serialize Hash to JSON document.
@@ -46,7 +59,7 @@ module GlobalSession
46
59
  # === Return
47
60
  # json(String):: A JSON-serialized representation of +value+
48
61
  def self.dump(object)
49
- return object.to_json
62
+ return ::JSON.dump(object)
50
63
  end
51
64
  end
52
65
 
@@ -54,11 +67,19 @@ module GlobalSession
54
67
  # for serializers.
55
68
  module Msgpack
56
69
  def self.load(binary)
57
- MessagePack.unpack(binary)
70
+ if defined?(MessagePack)
71
+ MessagePack.unpack(binary)
72
+ else
73
+ raise NotImplementedError, "::MessagePack undefined; please install 'msgpack' gem to enable this optional functionality"
74
+ end
58
75
  end
59
76
 
60
77
  def self.dump(object)
61
- object.to_msgpack
78
+ if object.respond_to?(:to_msgpack)
79
+ object.to_msgpack
80
+ else
81
+ raise NotImplementedError, "#to_msgpack unavailable; please install 'msgpack' gem to enable this optional functionality"
82
+ end
62
83
  end
63
84
  end
64
85
 
@@ -157,7 +157,6 @@ module GlobalSession
157
157
  return unless @directory.local_authority_name
158
158
  return if env['global_session.req.update'] == false
159
159
 
160
- domain = @configuration['cookie']['domain'] || env['SERVER_NAME']
161
160
  session = env['global_session']
162
161
 
163
162
  if session
@@ -172,11 +171,14 @@ module GlobalSession
172
171
  expires = @configuration['ephemeral'] ? nil : session.expired_at
173
172
  unless env['rack.cookies'][@cookie_name] == value
174
173
  env['rack.cookies'][@cookie_name] =
175
- {:value => value, :domain => domain, :expires => expires, :httponly=>true}
174
+ {:value => value,
175
+ :domain => cookie_domain(env),
176
+ :expires => expires,
177
+ :httponly=>true}
176
178
  end
177
179
  else
178
180
  # write an empty cookie
179
- env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
181
+ wipe_cookie(env)
180
182
  end
181
183
  rescue Exception => e
182
184
  wipe_cookie(env)
@@ -191,8 +193,9 @@ module GlobalSession
191
193
  return unless @directory.local_authority_name
192
194
  return if env['global_session.req.update'] == false
193
195
 
194
- domain = @configuration['cookie']['domain'] || env['SERVER_NAME']
195
- env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
196
+ env['rack.cookies'][@cookie_name] = {:value => nil,
197
+ :domain => cookie_domain(env),
198
+ :expires => Time.at(0)}
196
199
  end
197
200
 
198
201
  # Handle exceptions that occur during app invocation. This will either save the error
@@ -234,6 +237,26 @@ module GlobalSession
234
237
 
235
238
  true
236
239
  end
240
+
241
+ # Determine the domain name for which we should set the cookie. Uses the domain specified
242
+ # in the configuration if one is found; otherwise, uses the SERVER_NAME from the request
243
+ # but strips off the first component if the domain name contains more than two components.
244
+ #
245
+ # === Parameters
246
+ # env(Hash):: the Rack environment hash
247
+ def cookie_domain(env)
248
+ if @configuration['cookie'].key?('domain')
249
+ # Use the explicitly provided domain name
250
+ domain = @configuration['cookie']['domain']
251
+ else
252
+ # Use the server name, but strip off the most specific component
253
+ parts = env['SERVER_NAME'].split('.')
254
+ parts = parts[1..-1] if parts.length > 2
255
+ domain = parts.join('.')
256
+ end
257
+
258
+ domain
259
+ end
237
260
  end
238
261
  end
239
262
  end
@@ -20,6 +20,8 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'rack/contrib/cookies'
23
+
24
+ # lib/global_session.rb does this for us, but just in case this file is required directly...
23
25
  require 'action_pack'
24
26
  require 'action_controller'
25
27
 
@@ -15,10 +15,26 @@ module GlobalSession::Session
15
15
  # ExpiredSession:: if the session contained in the cookie has expired
16
16
  # MalformedCookie:: if the cookie was corrupt or malformed
17
17
  # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
18
- def initialize(directory)
18
+ def initialize(directory, cookie=nil)
19
19
  @directory = directory
20
20
  @signed = {}
21
21
  @insecure = {}
22
+
23
+ @configuration = directory.configuration
24
+ @schema_signed = Set.new((@configuration['attributes']['signed']))
25
+ @schema_insecure = Set.new((@configuration['attributes']['insecure']))
26
+
27
+ if cookie && !cookie.empty?
28
+ load_from_cookie(cookie)
29
+ elsif @directory.local_authority_name
30
+ create_from_scratch
31
+ else
32
+ create_invalid
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ inspect
22
38
  end
23
39
 
24
40
  # @return a representation of the object suitable for printing to the console
@@ -51,6 +67,45 @@ module GlobalSession::Session
51
67
  {}
52
68
  end
53
69
 
70
+ # @return [true,false] true if this session was created in-process, false if it was initialized from a cookie
71
+ def new_record?
72
+ @cookie.nil?
73
+ end
74
+
75
+ # Determine whether the session is valid. This method simply delegates to the
76
+ # directory associated with this session.
77
+ #
78
+ # === Return
79
+ # valid(true|false):: True if the session is valid, false otherwise
80
+ def valid?
81
+ @directory.valid_session?(@id, @expired_at)
82
+ end
83
+
84
+ # Determine whether the global session schema allows a given key to be placed
85
+ # in the global session.
86
+ #
87
+ # === Parameters
88
+ # key(String):: The name of the key
89
+ #
90
+ # === Return
91
+ # supported(true|false):: Whether the specified key is supported
92
+ def supports_key?(key)
93
+ @schema_signed.include?(key) || @schema_insecure.include?(key)
94
+ end
95
+
96
+ # Determine whether this session contains a value with the specified key.
97
+ #
98
+ # === Parameters
99
+ # key(String):: The name of the key
100
+ #
101
+ # === Return
102
+ # contained(true|false):: Whether the session currently has a value for the specified key.
103
+ def has_key?(key)
104
+ @signed.has_key?(key) || @insecure.has_key?(key)
105
+ end
106
+
107
+ alias :key? :has_key?
108
+
54
109
  # Invalidate this session by reporting its UUID to the Directory.
55
110
  #
56
111
  # === Return
@@ -79,5 +134,17 @@ module GlobalSession::Session
79
134
  raise GlobalSession::NoAuthority, 'Cannot change secure session attributes; we are not an authority'
80
135
  end
81
136
  end
137
+
138
+ def load_from_cookie(cookie)
139
+ raise NotImplementedError, "Subclass responsibility"
140
+ end
141
+
142
+ def create_from_scratch
143
+ raise NotImplementedError, "Subclass responsibility"
144
+ end
145
+
146
+ def create_invalid
147
+ raise NotImplementedError, "Subclass responsibility"
148
+ end
82
149
  end
83
150
  end
@@ -24,9 +24,20 @@ require 'set'
24
24
  require 'zlib'
25
25
 
26
26
  module GlobalSession::Session
27
- # Global session V1 uses JSON serialization and Zlib compression. Its encoding looks something
28
- # like this:
27
+ # V1 uses JSON serialization and Zlib compression. Its JSON structure is a Hash
28
+ # with the following format:
29
+ # {'id': <uuid_string> ,
30
+ # 'a': <signing_authority_string>,
31
+ # 'tc': <creation_timestamp_integer>,
32
+ # 'te': <expiration_timestamp_integer>,
33
+ # 'ds': {<signed_data_hash>},
34
+ # 'dx': {<unsigned_data_hash>},
35
+ # 's': <binary_signature_string>}
29
36
  #
37
+ # Limitations of V1 include the following:
38
+ # * Compressing the JSON usually INCREASES the size of the compressed data
39
+ # * The sign and verify algorithms, while safe, do not comply fully with PKCS7; they rely on the
40
+ # OpenSSL low-level crypto API instead of using the higher-level EVP (envelope) API.
30
41
  class V1 < Abstract
31
42
  # Utility method to decode a cookie; good for console debugging. This performs no
32
43
  # validation or security check of any sort.
@@ -39,53 +50,12 @@ module GlobalSession::Session
39
50
  return GlobalSession::Encoding::JSON.load(json)
40
51
  end
41
52
 
42
- # Create a new global session object.
43
- #
44
- # === Parameters
45
- # directory(Directory):: directory implementation that the session should use for various operations
46
- # cookie(String):: Optional, serialized global session cookie. If none is supplied, a new session is created.
47
- # valid_signature_digest(String):: Optional, already-trusted signature. If supplied, the expensive RSA-verify operation will be skipped if the cookie's signature matches the value supplied.
48
- #
49
- # ===Raise
50
- # InvalidSession:: if the session contained in the cookie has been invalidated
51
- # ExpiredSession:: if the session contained in the cookie has expired
52
- # MalformedCookie:: if the cookie was corrupt or malformed
53
- # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
54
- def initialize(directory, cookie=nil, valid_signature_digest=nil)
55
- super(directory)
56
- @configuration = directory.configuration
57
- @schema_signed = Set.new((@configuration['attributes']['signed']))
58
- @schema_insecure = Set.new((@configuration['attributes']['insecure']))
59
-
60
- if cookie && !cookie.empty?
61
- load_from_cookie(cookie, valid_signature_digest)
62
- elsif @directory.local_authority_name
63
- create_from_scratch
64
- else
65
- create_invalid
66
- end
67
- end
68
-
69
- # @return [true,false] true if this session was created in-process, false if it was initialized from a cookie
70
- def new_record?
71
- @cookie.nil?
72
- end
73
-
74
- # Determine whether the session is valid. This method simply delegates to the
75
- # directory associated with this session.
76
- #
77
- # === Return
78
- # valid(true|false):: True if the session is valid, false otherwise
79
- def valid?
80
- @directory.valid_session?(@id, @expired_at)
81
- end
82
-
83
53
  # Serialize the session to a form suitable for use with HTTP cookies. If any
84
54
  # secure attributes have changed since the session was instantiated, compute
85
55
  # a fresh RSA signature.
86
56
  #
87
57
  # === Return
88
- # cookie(String):: The B64cookie-encoded Zlib-compressed JSON-serialized global session hash
58
+ # cookie(String):: Base64Cookie-encoded, Zlib-compressed JSON-serialized global session
89
59
  def to_s
90
60
  if @cookie && !@dirty_insecure && !@dirty_secure
91
61
  #use cached cookie if nothing has changed
@@ -116,31 +86,6 @@ module GlobalSession::Session
116
86
  return GlobalSession::Encoding::Base64Cookie.dump(zbin)
117
87
  end
118
88
 
119
- # Determine whether the global session schema allows a given key to be placed
120
- # in the global session.
121
- #
122
- # === Parameters
123
- # key(String):: The name of the key
124
- #
125
- # === Return
126
- # supported(true|false):: Whether the specified key is supported
127
- def supports_key?(key)
128
- @schema_signed.include?(key) || @schema_insecure.include?(key)
129
- end
130
-
131
- # Determine whether this session contains a value with the specified key.
132
- #
133
- # === Parameters
134
- # key(String):: The name of the key
135
- #
136
- # === Return
137
- # contained(true|false):: Whether the session currently has a value for the specified key.
138
- def has_key?(key)
139
- @signed.has_key?(key) || @insecure.has_key?(key)
140
- end
141
-
142
- alias :key? :has_key?
143
-
144
89
  # Return the keys that are currently present in the global session.
145
90
  #
146
91
  # === Return
@@ -269,7 +214,7 @@ module GlobalSession::Session
269
214
  return output
270
215
  end
271
216
 
272
- def load_from_cookie(cookie, valid_signature_digest) # :nodoc:
217
+ def load_from_cookie(cookie) # :nodoc:
273
218
  begin
274
219
  zbin = GlobalSession::Encoding::Base64Cookie.load(cookie)
275
220
  json = Zlib::Inflate.inflate(zbin)
@@ -288,15 +233,13 @@ module GlobalSession::Session
288
233
  insecure = hash.delete('dx')
289
234
  signature = hash.delete('s')
290
235
 
291
- unless valid_signature_digest == digest(signature)
292
- #Check signature
293
- expected = canonical_digest(hash)
294
- signer = @directory.authorities[authority]
295
- raise SecurityError, "Unknown signing authority #{authority}" unless signer
296
- got = signer.public_decrypt(GlobalSession::Encoding::Base64Cookie.load(signature))
297
- unless (got == expected)
298
- raise SecurityError, "Signature mismatch on global session cookie; tampering suspected"
299
- end
236
+ #Check signature
237
+ expected = canonical_digest(hash)
238
+ signer = @directory.authorities[authority]
239
+ raise SecurityError, "Unknown signing authority #{authority}" unless signer
240
+ got = signer.public_decrypt(GlobalSession::Encoding::Base64Cookie.load(signature))
241
+ unless (got == expected)
242
+ raise SecurityError, "Signature mismatch on global session cookie; tampering suspected"
300
243
  end
301
244
 
302
245
  #Check trust in signing authority
@@ -22,10 +22,24 @@
22
22
  # Standard library dependencies
23
23
  require 'set'
24
24
 
25
- # Dependencies on other gems
26
- require 'msgpack'
27
-
28
25
  module GlobalSession::Session
26
+ # Global session V2 uses msgpack serialization and no compression. Its msgpack structure is an
27
+ # Array with the following format:
28
+ # [<uuid_string>,
29
+ # <signing_authority_string>,
30
+ # <creation_timestamp_integer>,
31
+ # <expiration_timestamp_integer>,
32
+ # {<signed_data_hash>},
33
+ # {<unsigned_data_hash>},
34
+ # <binary_signature_string>]
35
+ #
36
+ # The design goal of V2 is to minimize the size of the base64-encoded session state in order
37
+ # to make GlobalSession more amenable to use as a browser cookie.
38
+ #
39
+ # Limitations of V2 include the following:
40
+ # * Some Ruby implementations (e.g. JRuby) lack a msgpack library
41
+ # * The sign and verify algorithms, while safe, do not comply fully with PKCS7; they rely on the
42
+ # OpenSSL low-level crypto API instead of using the higher-level EVP (envelope) API.
29
43
  class V2 < Abstract
30
44
  # Utility method to decode a cookie; good for console debugging. This performs no
31
45
  # validation or security check of any sort.
@@ -37,53 +51,12 @@ module GlobalSession::Session
37
51
  return GlobalSession::Encoding::Msgpack.load(msgpack)
38
52
  end
39
53
 
40
- # Create a new global session object.
41
- #
42
- # === Parameters
43
- # directory(Directory):: directory implementation that the session should use for various operations
44
- # cookie(String):: Optional, serialized global session cookie. If none is supplied, a new session is created.
45
- # unused(Object):: Optional, already-trusted signature. This is ignored for v2.
46
- #
47
- # ===Raise
48
- # InvalidSession:: if the session contained in the cookie has been invalidated
49
- # ExpiredSession:: if the session contained in the cookie has expired
50
- # MalformedCookie:: if the cookie was corrupt or malformed
51
- # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
52
- def initialize(directory, cookie=nil)
53
- super(directory)
54
- @configuration = directory.configuration
55
- @schema_signed = Set.new((@configuration['attributes']['signed']))
56
- @schema_insecure = Set.new((@configuration['attributes']['insecure']))
57
-
58
- if cookie && !cookie.empty?
59
- load_from_cookie(cookie)
60
- elsif @directory.local_authority_name
61
- create_from_scratch
62
- else
63
- create_invalid
64
- end
65
- end
66
-
67
- # @return [true,false] true if this session was created in-process, false if it was initialized from a cookie
68
- def new_record?
69
- @cookie.nil?
70
- end
71
-
72
- # Determine whether the session is valid. This method simply delegates to the
73
- # directory associated with this session.
74
- #
75
- # === Return
76
- # valid(true|false):: True if the session is valid, false otherwise
77
- def valid?
78
- @directory.valid_session?(@id, @expired_at)
79
- end
80
-
81
54
  # Serialize the session to a form suitable for use with HTTP cookies. If any
82
55
  # secure attributes have changed since the session was instantiated, compute
83
56
  # a fresh RSA signature.
84
57
  #
85
58
  # === Return
86
- # cookie(String):: The B64cookie-encoded Zlib-compressed Msgpack-serialized global session hash
59
+ # cookie(String):: Base64Cookie-encoded, Msgpack-serialized global session
87
60
  def to_s
88
61
  if @cookie && !@dirty_insecure && !@dirty_secure
89
62
  #use cached cookie if nothing has changed
@@ -117,31 +90,6 @@ module GlobalSession::Session
117
90
  return GlobalSession::Encoding::Base64Cookie.dump(msgpack)
118
91
  end
119
92
 
120
- # Determine whether the global session schema allows a given key to be placed
121
- # in the global session.
122
- #
123
- # === Parameters
124
- # key(String):: The name of the key
125
- #
126
- # === Return
127
- # supported(true|false):: Whether the specified key is supported
128
- def supports_key?(key)
129
- @schema_signed.include?(key) || @schema_insecure.include?(key)
130
- end
131
-
132
- # Determine whether this session contains a value with the specified key.
133
- #
134
- # === Parameters
135
- # key(String):: The name of the key
136
- #
137
- # === Return
138
- # contained(true|false):: Whether the session currently has a value for the specified key.
139
- def has_key?(key)
140
- @signed.has_key?(key) || @insecure.has_key?(key)
141
- end
142
-
143
- alias :key? :has_key?
144
-
145
93
  # Return the keys that are currently present in the global session.
146
94
  #
147
95
  # === Return
@@ -314,12 +262,10 @@ module GlobalSession::Session
314
262
 
315
263
  begin
316
264
  signed_hash.verify!(signature, expired_at)
317
- rescue SecurityError => e
318
- if e.message =~ /expired/
319
- raise GlobalSession::ExpiredSession, "Session expired at #{expired_at}"
320
- else
321
- raise SecurityError, "Global session verification failure; suspected tampering: " + e.message
322
- end
265
+ rescue RightSupport::Crypto::ExpiredSignature
266
+ raise GlobalSession::ExpiredSession, "Session expired at #{expired_at}"
267
+ rescue RightSupport::Crypto::InvalidSignature => e
268
+ raise SecurityError, "Global session signature verification failed: " + e.message
323
269
  end
324
270
 
325
271
  #Check other validity (delegate to directory)