global_session 2.0.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)