has_global_session 0.9.5 → 1.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/README.rdoc CHANGED
@@ -176,10 +176,4 @@ class name of the directory under the 'common' section, like so:
176
176
  integrated: true
177
177
  directory: MyCoolDirectory
178
178
 
179
- = To-Do
180
-
181
- * Option to auto-renew session
182
-
183
- * Implement single sign-out via redirect
184
-
185
179
  Copyright (c) 2010 Tony Spataro <code@tracker.xeger.net>, released under the MIT license
@@ -7,8 +7,8 @@ spec = Gem::Specification.new do |s|
7
7
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
8
 
9
9
  s.name = 'has_global_session'
10
- s.version = '0.9.5'
11
- s.date = '2010-07-20'
10
+ s.version = '1.0'
11
+ s.date = '2010-07-27'
12
12
 
13
13
  s.authors = ['Tony Spataro']
14
14
  s.email = 'code@tracker.xeger.net'
@@ -1,16 +1,98 @@
1
1
  module HasGlobalSession
2
+ # Central point of access for HasGlobalSession configuration information. This is
3
+ # mostly a very thin wrapper around the serialized hash written to the YAML config
4
+ # file.
5
+ #
6
+ # The configuration is stored as a set of nested hashes and accessed by the code
7
+ # using hash lookup; for example, we might ask for +Configuration['cookie']['domain']+
8
+ # if we wanted to know which domain the cookie should be set for.
9
+ #
10
+ # The following settings are supported:
11
+ # * attributes
12
+ # * signed
13
+ # * insecure
14
+ # * integrated
15
+ # * ephemeral
16
+ # * timeout
17
+ # * renew
18
+ # * authority
19
+ # * trust
20
+ # * directory
21
+ # * cookie
22
+ # * name
23
+ # * domain
24
+ #
25
+ # === Environment-Specific Settings
26
+ # The top level of keys in the configuration hash are special; they provide different
27
+ # sections of settings that apply in different environments. For instance, a Rails
28
+ # application might have one set of settings that apply in the development environment;
29
+ # these would appear under +Configuration['development']+. Another set of settings would
30
+ # apply in the production environment and would appear under +Configuration['production']+.
31
+ #
32
+ # === Common Settings
33
+ # In addition to having one section for each operating environment, the configuration
34
+ # file can specify a 'common' section for settings that apply
35
+ #
36
+ # === Lookup Mechanism
37
+ # When the code asks for +Configuration['foo']+, we first check whether the current
38
+ # environment's config section has a value for foo. If one is found, we return that.
39
+ #
40
+ # If no environment-specific setting is found, we check the 'common' section and return
41
+ # the value found there.
42
+ #
43
+ # === Config File Location
44
+ # The name and location of the config file depend on the Web framework with which
45
+ # you are integrating; see HasGlobalSession::Rails for more information.
46
+ #
2
47
  module Configuration
48
+ # Reader for the environment module-attribute.
49
+ #
50
+ # === Return
51
+ # env(String):: The current configuration environment
3
52
  def self.environment; @environment; end
53
+
54
+ # Writer for the environment module-attribute.
55
+ #
56
+ # === Parameters
57
+ # value(String):: Configuration environment from which settings should be read
58
+ #
59
+ # === Return
60
+ # env(String):: The new configuration environment
4
61
  def self.environment=(value); @environment = value; end
5
62
 
63
+ # Reader for the config_file module-attribute.
64
+ #
65
+ # === Return
66
+ # file(String):: Absolute path to configuration file
6
67
  def self.config_file; @config_file; end
68
+
69
+ # Writer for the config_file module-attribute.
70
+ #
71
+ # === Parameters
72
+ # value(String):: Absolute path to configuration file
73
+ #
74
+ # === Return
75
+ # env(String):: The new path to the configuration file
7
76
  def self.config_file=(value); @config_file= value; end
8
77
 
78
+ # Reader for configuration elements. The reader first checks
79
+ # the current environment's settings section for the named
80
+ # value; if not found, it checks the common settings section.
81
+ #
82
+ # === Parameters
83
+ # name(Type):: Description
84
+ #
85
+ # === Return
86
+ # name(Type):: Description
87
+ #
88
+ # === Raise
89
+ # MissingConfiguration:: if config file location is unset, environment is unset, or config file is missing
90
+ # TypeError:: if config file does not contain a YAML-serialized Hash
9
91
  def self.[](key)
10
92
  get(key, true)
11
93
  end
12
94
 
13
- def self.validate
95
+ def self.validate # :nodoc
14
96
  ['attributes/signed', 'integrated', 'cookie/name', 'timeout'].each do |path|
15
97
  elements = path.split '/'
16
98
  object = get(elements.shift, false)
@@ -25,7 +107,8 @@ module HasGlobalSession
25
107
  end
26
108
 
27
109
  private
28
- def self.get(key, validated)
110
+
111
+ def self.get(key, validated) # :nodoc
29
112
  unless @config
30
113
  raise MissingConfiguration, "config_file is nil; cannot read configuration" unless config_file
31
114
  raise MissingConfiguration, "environment is nil; must be specified" unless environment
@@ -1,7 +1,43 @@
1
1
  module HasGlobalSession
2
+ # The global session directory, which provides some lookup and decision services
3
+ # to instances of GlobalSession.
4
+ #
5
+ # The default implementation is simplistic, but should be suitable for most applications.
6
+ # Directory is designed to be specialized via subclassing. To override the behavior to
7
+ # suit your needs, simply create a subclass of Directory and add a configuration file
8
+ # setting to specify the class name of your implementation:
9
+ #
10
+ # common:
11
+ # directory: MyCoolDirectory
12
+ #
13
+ #
14
+ # === The Authority Keystore
15
+ # Directory uses a filesystem directory as a backing store for RSA
16
+ # public keys of global session authorities. The directory should
17
+ # contain one or more +*.pub+ files containing OpenSSH-format public
18
+ # RSA keys. The name of the pub file determines the name of the
19
+ # authority it represents.
20
+ #
21
+ # === The Local Authority
22
+ # Directory will infer the name of the local authority (if any) by
23
+ # looking for a private-key file in the keystore. If a +*.key+ file
24
+ # is found, then its name is taken to be the name of the local
25
+ # authority and all GlobalSessions created will be signed by that
26
+ # authority's private key.
27
+ #
28
+ # If more than one key file is found, Directory will raise an error
29
+ # at initialization time.
30
+ #
2
31
  class Directory
3
32
  attr_reader :authorities, :private_key, :local_authority_name
4
33
 
34
+ # Create a new Directory.
35
+ #
36
+ # === Parameters
37
+ # keystore_directory(String):: Absolute path to authority keystore
38
+ #
39
+ # ===Raise
40
+ # ConfigurationError:: if too many or too few keys are found, or if *.key/*.pub files are malformatted
5
41
  def initialize(keystore_directory)
6
42
  certs = Dir[File.join(keystore_directory, '*.pub')]
7
43
  keys = Dir[File.join(keystore_directory, '*.key')]
@@ -24,14 +60,41 @@ module HasGlobalSession
24
60
  end
25
61
  end
26
62
 
63
+ # Determine whether this system trusts a particular authority based on
64
+ # the trust settings specified in Configuration.
65
+ #
66
+ # === Parameters
67
+ # authority(String):: The name of the authority
68
+ #
69
+ # === Return
70
+ # trusted(true|false):: whether the local system trusts sessions signed by the specified authority
27
71
  def trusted_authority?(authority)
28
72
  Configuration['trust'].include?(authority)
29
73
  end
30
74
 
75
+ # Determine whether the given session UUID is valid. The default implementation only considers
76
+ # a session to be invalid if its expired_at timestamp is in the past. Custom implementations
77
+ # might want to consider other factors, such as whether the user has signed out of this node
78
+ # or another node (perhaps using some sort of centralized lookup or single sign-out mechanism).
79
+ #
80
+ # === Parameters
81
+ # uuid(String):: Global session UUID
82
+ # expired_at(Time):: When the session expired (or will expire)
83
+ #
84
+ # === Return
85
+ # valid(true|false):: whether the specified session is valid
31
86
  def valid_session?(uuid, expired_at)
32
87
  expired_at > Time.now
33
88
  end
34
89
 
90
+ # Callback used by GlobalSession objects to report when the application code calls
91
+ # #invalidate! on them. The default implementation of this method does nothing.
92
+ #
93
+ # uuid(String):: Global session UUID
94
+ # expired_at(Time):: When the session expired
95
+ #
96
+ # === Return
97
+ # true:: Always returns true
35
98
  def report_invalid_session(uuid, expired_at)
36
99
  true
37
100
  end
@@ -1,10 +1,29 @@
1
1
  module HasGlobalSession
2
+ # Various encoding (not encryption!) techniques used by the global session plugin.
3
+ #
2
4
  module Encoding
5
+ # JSON serializer, used to serialize Hash objects in a form suitable
6
+ # for stuffing into a cookie.
7
+ #
3
8
  class JSON
9
+ # Unserialize JSON to Hash.
10
+ #
11
+ # === Parameters
12
+ # json(String):: A well-formed JSON document
13
+ #
14
+ # === Return
15
+ # value(Hash):: An unserialized Ruby Hash
4
16
  def self.load(json)
5
17
  ::JSON.load(json)
6
18
  end
7
19
 
20
+ # Serialize Hash to JSON document.
21
+ #
22
+ # === Parameters
23
+ # value(Hash):: The hash to be serialized
24
+ #
25
+ # === Return
26
+ # json(String):: A JSON-serialized representation of +value+
8
27
  def self.dump(object)
9
28
  return object.to_json
10
29
  end
@@ -21,11 +40,25 @@ module HasGlobalSession
21
40
  # this scheme preserves the '=' padding characters due to limitations of
22
41
  # Ruby's built-in base64 encoding routines.
23
42
  class Base64Cookie
43
+ # Decode a B64cookie-encoded string.
44
+ #
45
+ # === Parameters
46
+ # encoded(String):: The encoded string
47
+ #
48
+ # === Return
49
+ # decoded(String):: The decoded result, which may contain nonprintable bytes
24
50
  def self.load(string)
25
51
  tr = string.tr('-_', '+/')
26
52
  return tr.unpack('m')[0]
27
53
  end
28
54
 
55
+ # Encode a Ruby (ASCII or binary) string.
56
+ #
57
+ # === Parameters
58
+ # decoded(String):: The raw string to be encoded
59
+ #
60
+ # === Return
61
+ # encoded(String):: The B64cookie-encoded result.
29
62
  def self.dump(object)
30
63
  raw = [object].pack('m')
31
64
  raw.tr!('+/', '-_')
@@ -6,15 +6,34 @@ require 'zlib'
6
6
  require 'uuidtools'
7
7
 
8
8
  module HasGlobalSession
9
+ # Ladies and gentlemen: the one and only, star of the show, GLOBAL SESSION!
10
+ #
11
+ # GlobalSession is designed to act as much like a Hash as possible. You can use
12
+ # most of the methods you would use with Hash: [], has_key?, each, etc. It has a
13
+ # few additional methods that are specific to itself, mostly involving whether
14
+ # it's expired, valid, supports a certain key, etc.
15
+ #
9
16
  class GlobalSession
10
17
  attr_reader :id, :authority, :created_at, :expired_at, :directory
11
18
 
19
+ # Create a new global session object.
20
+ #
21
+ # === Parameters
22
+ # directory(Directory):: directory implementation that the session should use for various operations
23
+ # cookie(String):: Optional, serialized global session cookie. If none is supplied, a new session is created.
24
+ # 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.
25
+ #
26
+ # ===Raise
27
+ # InvalidSession:: if the session contained in the cookie has been invalidated
28
+ # ExpiredSession:: if the session contained in the cookie has expired
29
+ # MalformedCookie:: if the cookie was corrupt or malformed
30
+ # SecurityError:: if signature is invalid or cookie is not signed by a trusted authority
12
31
  def initialize(directory, cookie=nil, valid_signature_digest=nil)
13
32
  @schema_signed = Set.new((Configuration['attributes']['signed']))
14
33
  @schema_insecure = Set.new((Configuration['attributes']['insecure']))
15
34
  @directory = directory
16
35
 
17
- if cookie
36
+ if cookie && !cookie.empty?
18
37
  load_from_cookie(cookie, valid_signature_digest)
19
38
  elsif @directory.local_authority_name
20
39
  create_from_scratch
@@ -23,10 +42,21 @@ module HasGlobalSession
23
42
  end
24
43
  end
25
44
 
45
+ # Determine whether the session is valid. This method simply delegates to the
46
+ # directory associated with this session.
47
+ #
48
+ # === Return
49
+ # valid(true|false):: True if the session is valid, false otherwise
26
50
  def valid?
27
51
  @directory.valid_session?(@id, @expired_at)
28
52
  end
29
53
 
54
+ # Serialize the session to a form suitable for use with HTTP cookies. If any
55
+ # secure attributes have changed since the session was instantiated, compute
56
+ # a fresh RSA signature.
57
+ #
58
+ # === Return
59
+ # cookie(String):: The B64cookie-encoded Zlib-compressed JSON-serialized global session hash
30
60
  def to_s
31
61
  if @cookie && !@dirty_insecure && !@dirty_secure
32
62
  #use cached cookie if nothing has changed
@@ -57,35 +87,84 @@ module HasGlobalSession
57
87
  return Encoding::Base64Cookie.dump(zbin)
58
88
  end
59
89
 
90
+ # Determine whether the global session schema allows a given key to be placed
91
+ # in the global session.
92
+ #
93
+ # === Parameters
94
+ # key(String):: The name of the key
95
+ #
96
+ # === Return
97
+ # supported(true|false):: Whether the specified key is supported
60
98
  def supports_key?(key)
61
99
  @schema_signed.include?(key) || @schema_insecure.include?(key)
62
100
  end
63
101
 
102
+ # Determine whether this session contains a value with the specified key.
103
+ #
104
+ # === Parameters
105
+ # key(String):: The name of the key
106
+ #
107
+ # === Return
108
+ # contained(true|false):: Whether the session currently has a value for the specified key.
64
109
  def has_key?(key)
65
110
  @signed.has_key(key) || @insecure.has_key?(key)
66
111
  end
67
112
 
68
- def signature_digest
69
- @signature ? digest(@signature) : nil
70
- end
71
-
113
+ # Return the keys that are currently present in the global session.
114
+ #
115
+ # === Return
116
+ # keys(Array):: List of keys contained in the global session
72
117
  def keys
73
118
  @signed.keys + @insecure.keys
74
119
  end
75
120
 
121
+ # Return the values that are currently present in the global session.
122
+ #
123
+ # === Return
124
+ # values(Array):: List of values contained in the global session
76
125
  def values
77
126
  @signed.values + @insecure.values
78
127
  end
79
128
 
80
- def each_pair(&block)
129
+ # Iterate over each key/value pair
130
+ #
131
+ # === Block
132
+ # An iterator which will be called with each key/value pair
133
+ #
134
+ # === Return
135
+ # Returns the value of the last expression evaluated by the block
136
+ def each_pair(&block) # :yields: |key, value|
81
137
  @signed.each_pair(&block)
82
138
  @insecure.each_pair(&block)
83
139
  end
84
140
 
141
+ # Lookup a value by its key.
142
+ #
143
+ # === Parameters
144
+ # key(String):: the key
145
+ #
146
+ # === Return
147
+ # value(Object):: The value associated with +key+, or nil if +key+ is not present
85
148
  def [](key)
86
149
  @signed[key] || @insecure[key]
87
150
  end
88
151
 
152
+ # Set a value in the global session hash. If the supplied key is denoted as
153
+ # secure by the global session schema, causes a new signature to be computed
154
+ # when the session is next serialized.
155
+ #
156
+ # === Parameters
157
+ # key(String):: The key to set
158
+ # value(Object):: The value to set
159
+ #
160
+ # === Return
161
+ # value(Object):: Always returns the value that was set
162
+ #
163
+ # ===Raise
164
+ # InvalidSession:: if the session has been invalidated (and therefore can't be written to)
165
+ # ArgumentError:: if the configuration doesn't define the specified key as part of the global session
166
+ # NoAuthority:: if the specified key is secure and the local node is not an authority
167
+ # UnserializableType:: if the specified value can't be serialized as JSON
89
168
  def []=(key, value)
90
169
  raise InvalidSession unless valid?
91
170
 
@@ -102,36 +181,58 @@ module HasGlobalSession
102
181
  else
103
182
  raise ArgumentError, "Attribute '#{key}' is not specified in global session configuration"
104
183
  end
184
+
185
+ return value
105
186
  end
106
187
 
188
+ # Invalidate this session by reporting its UUID to the Directory.
189
+ #
190
+ # === Return
191
+ # unknown(Object):: Returns whatever the Directory returns
107
192
  def invalidate!
108
193
  @directory.report_invalid_session(@id, @expired_at)
109
194
  end
110
195
 
196
+ # Renews this global session, changing its expiry timestamp into the future.
197
+ # Causes a new signature will be computed when the session is next serialized.
198
+ #
199
+ # === Return
200
+ # true:: Always returns true
111
201
  def renew!
112
202
  authority_check
113
203
  @expired_at = Configuration['timeout'].to_i.minutes.from_now.utc
114
204
  @dirty_secure = true
115
205
  end
116
206
 
207
+ # Return the SHA1 hash of the most recently-computed RSA signature of this session.
208
+ # This isn't really intended for the end user; it exists so the Web framework integration
209
+ # code can optimize request speed by caching the most recently verified signature in the
210
+ # local session and avoid re-verifying it on every request.
211
+ #
212
+ # === Return
213
+ # digest(String):: SHA1 hex-digest of most-recently-computed signature
214
+ def signature_digest
215
+ @signature ? digest(@signature) : nil
216
+ end
217
+
117
218
  private
118
219
 
119
- def authority_check
220
+ def authority_check # :nodoc:
120
221
  unless @directory.local_authority_name
121
222
  raise NoAuthority, 'Cannot change secure session attributes; we are not an authority'
122
223
  end
123
224
  end
124
225
 
125
- def canonical_digest(input)
226
+ def canonical_digest(input) # :nodoc:
126
227
  canonical = Encoding::JSON.dump(canonicalize(input))
127
228
  return digest(canonical)
128
229
  end
129
230
 
130
- def digest(input)
231
+ def digest(input) # :nodoc:
131
232
  return Digest::SHA1.new().update(input).hexdigest
132
233
  end
133
234
 
134
- def canonicalize(input)
235
+ def canonicalize(input) # :nodoc:
135
236
  case input
136
237
  when Hash
137
238
  output = Array.new
@@ -150,10 +251,16 @@ module HasGlobalSession
150
251
  return output
151
252
  end
152
253
 
153
- def load_from_cookie(cookie, valid_signature_digest)
154
- zbin = Encoding::Base64Cookie.load(cookie)
155
- json = Zlib::Inflate.inflate(zbin)
156
- hash = Encoding::JSON.load(json)
254
+ def load_from_cookie(cookie, valid_signature_digest) # :nodoc:
255
+ begin
256
+ zbin = Encoding::Base64Cookie.load(cookie)
257
+ json = Zlib::Inflate.inflate(zbin)
258
+ hash = Encoding::JSON.load(json)
259
+ rescue Exception => e
260
+ mc = MalformedCookie.new("Caused by #{e.class.name}: #{e.message}")
261
+ mc.set_backtrace(e.backtrace)
262
+ raise mc
263
+ end
157
264
 
158
265
  id = hash['id']
159
266
  authority = hash['a']
@@ -200,7 +307,7 @@ module HasGlobalSession
200
307
  @cookie = cookie
201
308
  end
202
309
 
203
- def create_from_scratch
310
+ def create_from_scratch # :nodoc:
204
311
  authority_check
205
312
 
206
313
  @signed = {}
@@ -219,7 +326,7 @@ module HasGlobalSession
219
326
  renew!
220
327
  end
221
328
 
222
- def create_invalid
329
+ def create_invalid # :nodoc:
223
330
  @id = nil
224
331
  @created_at = Time.now.utc
225
332
  @expired_at = created_at
@@ -1,12 +1,42 @@
1
1
  module HasGlobalSession
2
+ # Helper class that enables the end user to treat the global and local session as if
3
+ # they were the same object. This is accomplished by implementing approximately the
4
+ # same interface as a Hash, and dispatching to one or the other session object depending
5
+ # on various factors.
6
+ #
7
+ # This class isn't intended to be used directly by the end user. Instead, set integrated: true
8
+ # in the configuration file and the Web framework integration code will manage an integrated
9
+ # session object for you, as well as overriding the framework's default session accessor to
10
+ # return an integrated session instead.
11
+ #
12
+ # When using an integrated session, you can always get to the underlying objects by
13
+ # using the #local and #global readers of this class.
14
+ #
2
15
  class IntegratedSession
3
- attr_reader :local, :global
16
+ # Return the local-session objects, whose type may vary depending on the Web framework.
17
+ attr_reader :local
18
+
19
+ # Return the global-session object.
20
+ attr_reader :global
4
21
 
22
+ # Construct a new integrated session.
23
+ #
24
+ # === Parameters
25
+ # local(Object):: Local session that acts like a Hash
26
+ # global(GlobalSession):: GlobalSession
5
27
  def initialize(local, global)
6
28
  @local = local
7
29
  @global = global
8
30
  end
9
31
 
32
+ # Retrieve a value from the global session if the supplied key is supported by
33
+ # the global session, else retrieve it from the local session.
34
+ #
35
+ # === Parameters
36
+ # key(String):: the key
37
+ #
38
+ # === Return
39
+ # value(Object):: The value associated with +key+, or nil if +key+ is not present
10
40
  def [](key)
11
41
  key = key.to_s
12
42
  if @global.supports_key?(key)
@@ -16,6 +46,15 @@ module HasGlobalSession
16
46
  end
17
47
  end
18
48
 
49
+ # Set a value in the global session (if the supplied key is supported) or the local
50
+ # session otherwise.
51
+ #
52
+ # === Parameters
53
+ # key(String):: The key to set
54
+ # value(Object):: The value to set
55
+ #
56
+ # === Return
57
+ # value(Object):: Always returns the value that was set
19
58
  def []=(key, value)
20
59
  key = key.to_s
21
60
  if @global.supports_key?(key)
@@ -23,34 +62,48 @@ module HasGlobalSession
23
62
  else
24
63
  @local[key] = value
25
64
  end
65
+
66
+ return value
26
67
  end
27
68
 
69
+ # Determine whether the global or local session contains a value with the specified key.
70
+ #
71
+ # === Parameters
72
+ # key(String):: The name of the key
73
+ #
74
+ # === Return
75
+ # contained(true|false):: Whether the session currently has a value for the specified key.
28
76
  def has_key?(key)
29
77
  key = key.to_s
30
- @global.has_key(key) || @local.has_key?(key)
78
+ @global.has_key?(key) || @local.has_key?(key)
31
79
  end
32
80
 
81
+ # Return the keys that are currently present in either the global or local session.
82
+ #
83
+ # === Return
84
+ # keys(Array):: List of keys contained in the global or local session.
33
85
  def keys
34
86
  @global.keys + @local.keys
35
87
  end
36
88
 
89
+ # Return the values that are currently present in the global or local session.
90
+ #
91
+ # === Return
92
+ # values(Array):: List of values contained in the global or local session.
37
93
  def values
38
94
  @global.values + @local.values
39
95
  end
40
96
 
97
+ # Iterate over each key/value pair in both the global and local session.
98
+ #
99
+ # === Block
100
+ # An iterator which will be called with each key/value pair
101
+ #
102
+ # === Return
103
+ # Returns the value of the last expression evaluated by the block
41
104
  def each_pair(&block)
42
105
  @global.each_pair(&block)
43
106
  @local.each_pair(&block)
44
107
  end
45
-
46
- def method_missing(meth, *args)
47
- if @global.respond_to?(meth)
48
- return @global.send(meth, *args)
49
- elsif @local.respond_to?(meth)
50
- return @local.send(meth, *args)
51
- else
52
- super
53
- end
54
- end
55
108
  end
56
109
  end
@@ -1,17 +1,39 @@
1
1
  module HasGlobalSession
2
+ # Rails integration for HasGlobalSession.
3
+ #
4
+ # The configuration file for Rails apps is located in +config/global_session.yml+ and a generator
5
+ # (global_session_config) is available for creating a sensible default.
6
+ #
7
+ # There is also a generator (global_session_authority) for creating authority keypairs.
8
+ #
9
+ # The main integration touchpoint for Rails is the module ActionControllerInstanceMethods,
10
+ # which gets mixed into ActionController::Base. This is where all of the magic happens..
11
+ #
2
12
  module Rails
13
+ # Module that is mixed into ActionController-derived classes when the class method
14
+ # +has_global_session+ is called.
15
+ #
3
16
  module ActionControllerInstanceMethods
4
- def self.included(base)
17
+ def self.included(base) # :nodoc:
5
18
  base.alias_method_chain :session, :global_session
6
19
  base.before_filter :global_session_read_cookie
7
20
  base.before_filter :global_session_auto_renew
8
21
  base.after_filter :global_session_update_cookie
9
22
  end
10
23
 
24
+ # Global session reader.
25
+ #
26
+ # === Return
27
+ # session(GlobalSession):: the global session associated with the current request, nil if none
11
28
  def global_session
12
29
  @global_session
13
30
  end
14
31
 
32
+ # Aliased version of ActionController::Base#session which will return the integrated
33
+ # global-and-local session object (IntegratedSession).
34
+ #
35
+ # === Return
36
+ # session(IntegratedSession):: the integrated session
15
37
  def session_with_global_session
16
38
  if Configuration['integrated'] && @global_session
17
39
  unless @integrated_session &&
@@ -27,6 +49,11 @@ module HasGlobalSession
27
49
  end
28
50
  end
29
51
 
52
+ # Before-filter to read the global session cookie and construct the GlobalSession object
53
+ # for this controller instance.
54
+ #
55
+ # === Return
56
+ # true:: Always returns true
30
57
  def global_session_read_cookie
31
58
  directory = global_session_create_directory
32
59
  cookie_name = Configuration['cookie']['name']
@@ -56,15 +83,27 @@ module HasGlobalSession
56
83
  end
57
84
  end
58
85
 
86
+ # Before-filter to renew the global session if it will be expiring soon.
87
+ #
88
+ # === Return
89
+ # true:: Always returns true
59
90
  def global_session_auto_renew
60
91
  #Auto-renew session if needed
61
92
  renew = Configuration['renew']
62
- if @global_session.directory.local_authority_name && renew &&
93
+ if @global_session &&
94
+ renew &&
95
+ @global_session.directory.local_authority_name &&
63
96
  @global_session.expired_at < renew.to_i.minutes.from_now.utc
64
97
  @global_session.renew!
65
- end
98
+ end
99
+
100
+ return true
66
101
  end
67
102
 
103
+ # After-filter to write any pending changes to the global session cookie.
104
+ #
105
+ # === Return
106
+ # true:: Always returns true
68
107
  def global_session_update_cookie
69
108
  name = Configuration['cookie']['name']
70
109
  domain = Configuration['cookie']['domain'] || request.env['SERVER_NAME']
@@ -90,6 +129,15 @@ module HasGlobalSession
90
129
  end
91
130
  end
92
131
 
132
+ # Override for the ActionController method of the same name that logs
133
+ # information about the request. Our version logs the global session ID
134
+ # instead of the local session ID.
135
+ #
136
+ # === Parameters
137
+ # name(Type):: Description
138
+ #
139
+ # === Return
140
+ # name(Type):: Description
93
141
  def log_processing
94
142
  if logger && logger.info?
95
143
  log_processing_for_request_id
@@ -97,7 +145,7 @@ module HasGlobalSession
97
145
  end
98
146
  end
99
147
 
100
- def log_processing_for_request_id
148
+ def log_processing_for_request_id # :nodoc:
101
149
  if global_session && global_session.id
102
150
  session_id = global_session.id + " (#{session[:session_id]})"
103
151
  elsif session[:session_id]
@@ -114,7 +162,7 @@ module HasGlobalSession
114
162
  logger.info(request_id)
115
163
  end
116
164
 
117
- def log_processing_for_parameters
165
+ def log_processing_for_parameters # :nodoc:
118
166
  parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
119
167
  parameters = parameters.except!(:controller, :action, :format, :_method)
120
168
 
@@ -123,7 +171,7 @@ module HasGlobalSession
123
171
 
124
172
  private
125
173
 
126
- def global_session_create_directory
174
+ def global_session_create_directory # :nodoc:
127
175
  if (klass = Configuration['directory'])
128
176
  klass = klass.constantize
129
177
  else
@@ -1,9 +1,46 @@
1
1
  module HasGlobalSession
2
+ # Indicates that the global session configuration file is missing from disk.
3
+ #
2
4
  class MissingConfiguration < Exception; end
5
+
6
+ # Indicates that the global session configuration file is missing elements or is
7
+ # malformatted.
8
+ #
3
9
  class ConfigurationError < Exception; end
10
+
11
+ # Indicates that a client submitted a request with a valid session cookie, but the
12
+ # session ID was reported as invalid by the Directory.
13
+ #
14
+ # See Directory#valid_session? for more information.
15
+ #
4
16
  class InvalidSession < Exception; end
17
+
18
+ # Indicates that a client submitted a request with a valid session cookie, but the
19
+ # session has expired.
20
+ #
5
21
  class ExpiredSession < Exception; end
22
+
23
+ # Indicates that a client submitted a request with a session cookie that could not
24
+ # be decoded or decompressed.
25
+ #
26
+ class MalformedCookie < Exception; end
27
+
28
+ # Indicates that application code tried to put an unserializable object into the glboal
29
+ # session hash. Because the global session is serialized as JSON and not all Ruby types
30
+ # can be easily round-tripped to JSON and back without data loss, we constrain the types
31
+ # that can be serialized.
32
+ #
33
+ # See HasGlobalSession::Encoding::JSON for more information on serializable types.
34
+ #
6
35
  class UnserializableType < Exception; end
36
+
37
+ # Indicates that the application code tried to write a secure session attribute or
38
+ # renew the global session. Both of these operations require a local authority
39
+ # because they require a new signature to be computed on the global session.
40
+ #
41
+ # See HasGlobalSession::Configuration and HasGlobalSession::Directory for more
42
+ # information.
43
+ #
7
44
  class NoAuthority < Exception; end
8
45
  end
9
46
 
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_global_session
3
3
  version: !ruby/object:Gem::Version
4
- hash: 49
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
+ - 1
7
8
  - 0
8
- - 9
9
- - 5
10
- version: 0.9.5
9
+ version: "1.0"
11
10
  platform: ruby
12
11
  authors:
13
12
  - Tony Spataro
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-07-20 00:00:00 -07:00
17
+ date: 2010-07-27 00:00:00 -07:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency