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 +1 -1
- data/VERSION +1 -1
- data/global_session.gemspec +26 -25
- data/lib/global_session/directory.rb +10 -8
- data/lib/global_session/encoding.rb +25 -4
- data/lib/global_session/rack.rb +28 -5
- data/lib/global_session/rails.rb +2 -0
- data/lib/global_session/session/abstract.rb +68 -1
- data/lib/global_session/session/v1.rb +22 -79
- data/lib/global_session/session/v2.rb +22 -76
- data/lib/global_session/session/v3.rb +373 -0
- data/lib/global_session/session.rb +20 -7
- data/lib/global_session.rb +2 -4
- metadata +80 -71
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.0
|
data/global_session.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{global_session}
|
8
|
-
s.version = "
|
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.
|
63
|
-
s.
|
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<
|
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<
|
78
|
-
s.add_dependency(%q<
|
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<
|
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<
|
94
|
-
s.add_dependency(%q<
|
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<
|
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
|
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
|
122
|
+
Session::V1.new(self, cookie)
|
120
123
|
else
|
121
|
-
|
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
|
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
|
144
|
-
Session.new(self, cookie
|
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.
|
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
|
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
|
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
|
|
data/lib/global_session/rack.rb
CHANGED
@@ -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,
|
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
|
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
|
-
|
195
|
-
|
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
|
data/lib/global_session/rails.rb
CHANGED
@@ -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
|
-
#
|
28
|
-
#
|
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)::
|
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
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
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)::
|
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
|
318
|
-
|
319
|
-
|
320
|
-
|
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)
|