global_session 3.0.5 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,6 +19,8 @@
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
+ require 'yaml'
23
+
22
24
  module GlobalSession
23
25
  # Central point of access for GlobalSession configuration information. This is
24
26
  # mostly a very thin wrapper around the serialized hash written to the YAML config
@@ -35,8 +37,8 @@ module GlobalSession
35
37
  # * ephemeral
36
38
  # * timeout
37
39
  # * renew
38
- # * authority
39
- # * trust
40
+ # * authority (optional - inferred from presence of private key file)
41
+ # * trust (optional - inferred from presence/name of public key files)
40
42
  # * directory
41
43
  # * cookie
42
44
  # * version
@@ -94,7 +96,7 @@ module GlobalSession
94
96
  def initialize(config, environment)
95
97
  if config.is_a?(Hash)
96
98
  @config = config
97
- elsif File.readable?(config)
99
+ elsif File.file?(config)
98
100
  data = YAML.load(File.read(config))
99
101
  unless data.is_a?(Hash)
100
102
  raise TypeError, "Configuration file #{File.basename(config)} must contain a hash as its top-level element"
@@ -121,9 +123,32 @@ module GlobalSession
121
123
  get(key, true)
122
124
  end
123
125
 
126
+ # Writer for configuration elements. Writes to an environment-specific stanza if one is present,
127
+ # else writes to the common stanza. DOES NOT OVERWRITE the key's value if it already has one!
128
+ #
129
+ # @param [String] key
130
+ # @param optional [Object] the value to write, or empty-hash as a default
131
+ def []=(key, value={})
132
+ if @config.has_key?(@environment)
133
+ @config[@environment][key] ||= value
134
+ else
135
+ @config['common'][key] ||= value
136
+ end
137
+ rescue NoMethodError
138
+ raise MissingConfiguration, "Configuration key '#{key}' not found"
139
+ end
140
+
141
+ # Determine whether a given configuration key was specified.
142
+ #
143
+ # @return [Boolean] true if the key is present in the common or per-environment stanzas
144
+ def has_key?(k)
145
+ @config[@environment].has_key?(k) || @config['common'].has_key?(k)
146
+ end
147
+
148
+ alias key? has_key?
149
+
124
150
  def validate # :nodoc
125
- ['attributes/signed', 'cookie/name',
126
- 'timeout'].each {|k| validate_presence_of k}
151
+ ['attributes/signed', 'cookie/name', 'timeout'].each {|k| validate_presence_of k}
127
152
  end
128
153
 
129
154
  protected
@@ -137,7 +162,12 @@ module GlobalSession
137
162
  # true always
138
163
  def validate_presence_of(key)
139
164
  elements = key.split '/'
140
- object = get(elements.shift, false)
165
+ top_key = elements.shift
166
+ object = get(top_key, true) # pretend we're validated in order to get inheritance
167
+ if object.nil?
168
+ msg = "Configuration does not specify required element '#{top_key}'"
169
+ raise MissingConfiguration, msg
170
+ end
141
171
  elements.each do |element|
142
172
  object = object[element] if object
143
173
  if object.nil?
@@ -150,15 +180,32 @@ module GlobalSession
150
180
 
151
181
  private
152
182
 
183
+ # Get a configuration key.
184
+ #
185
+ # @return [Object] the value of the desired key
186
+ # @raise [MissingConfiguration] if the key is not found
187
+ # @param [String] key
188
+ # @param [Boolean] if true, check both the common and per-environment stanzas for the key
153
189
  def get(key, validated) # :nodoc
154
- if @config.has_key?(@environment) &&
155
- @config[@environment].has_key?(key)
156
- return @config[@environment][key]
190
+ if validated
191
+ # Fancy inheritance logic
192
+ if ('common' == key) && @config.key?(key)
193
+ # The common stanza itself
194
+ @config[key]
195
+ elsif (@environment == key) && @config.key?(key)
196
+ # The environment-specific stanza itself
197
+ @config[key]
198
+ elsif @config.key?(@environment) && @config[@environment].key?(key)
199
+ # Some key in the environment-specific stanza
200
+ return @config[@environment][key]
201
+ elsif @config.key?('common') && @config['common'].key?(key)
202
+ # By process of elimination, some key in the common stanza
203
+ @config['common'][key]
204
+ end
157
205
  else
158
- @config['common'][key]
206
+ # Fail sauce
207
+ raise MissingConfiguration, "Configuration key '#{key}' not found"
159
208
  end
160
- rescue NoMethodError
161
- raise MissingConfiguration, "Configuration key '#{key}' not found"
162
209
  end
163
210
  end
164
211
  end
@@ -22,7 +22,7 @@
22
22
  require 'set'
23
23
 
24
24
  module GlobalSession
25
- # The global session directory, which provides some lookup and decision services
25
+ # The global session directory, which provides lookup and decision services
26
26
  # to instances of Session.
27
27
  #
28
28
  # The default implementation is simplistic, but should be suitable for most applications.
@@ -31,28 +31,23 @@ module GlobalSession
31
31
  # setting to specify the class name of your implementation:
32
32
  #
33
33
  # common:
34
- # directory: MyCoolDirectory
34
+ # directory:
35
+ # class: MyCoolDirectory
35
36
  #
37
+ # == Key Management
36
38
  #
37
- # === The Authority Keystore
38
- # Directory uses a filesystem directory as a backing store for RSA
39
- # public keys of global session authorities. The directory should
40
- # contain one or more +*.pub+ files containing OpenSSH-format public
41
- # RSA keys. The name of the pub file determines the name of the
42
- # authority it represents.
39
+ # All key-related functionality has been delegated to the Keystore class as of
40
+ # v3.1. Directory retains its key management hooks for downrev compatibility,
41
+ # but mostly they are stubs for Keystore functionality.
43
42
  #
44
- # === The Local Authority
45
- # Directory will infer the name of the local authority (if any) by
46
- # looking for a private-key file in the keystore. If a +*.key+ file
47
- # is found, then its name is taken to be the name of the local
48
- # authority and all GlobalSessions created will be signed by that
49
- # authority's private key.
50
- #
51
- # If more than one key file is found, Directory will raise an error
52
- # at initialization time.
43
+ # For more information about key mangement, please refer to the Keystore class.
53
44
  #
54
45
  class Directory
55
- attr_reader :configuration, :authorities, :private_key
46
+ # @return [Configuration] shared configuration object
47
+ attr_reader :configuration
48
+
49
+ # @return [Keystore] asymmetric crypto keys for signing authorities
50
+ attr_reader :keystore
56
51
 
57
52
  # @return a representation of the object suitable for printing to the console
58
53
  def inspect
@@ -61,31 +56,36 @@ module GlobalSession
61
56
 
62
57
  # Create a new Directory.
63
58
  #
64
- # === Parameters
65
- # keystore_directory(String):: Absolute path to authority keystore
66
- #
67
- # === Raise
68
- # ConfigurationError:: if too many or too few keys are found, or if *.key/*.pub files are malformatted
69
- def initialize(configuration, keystore_directory)
59
+ # @param [Configuration] shared configuration
60
+ # @param optional [String] keystore_directory (DEPRECATED) if present, directory where keys can be found
61
+ # @raise [ConfigurationError] if too many or too few keys are found, or if *.key/*.pub files are malformatted
62
+ def initialize(configuration, keystore_directory=nil)
70
63
  @configuration = configuration
71
- certs = Dir[File.join(keystore_directory, '*.pub')]
72
- keys = Dir[File.join(keystore_directory, '*.key')]
73
-
74
64
  @authorities = {}
75
- certs.each do |cert_file|
76
- basename = File.basename(cert_file)
77
- authority = basename[0...(basename.rindex('.'))] #chop trailing .ext
78
- @authorities[authority] = OpenSSL::PKey::RSA.new(File.read(cert_file))
79
- raise ConfigurationError, "Expected #{basename} to contain an RSA public key" unless @authorities[authority].public?
80
- end
81
65
 
82
- if local_authority_name
83
- key_file = keys.detect { |kf| kf =~ /#{local_authority_name}.key$/ }
84
- raise ConfigurationError, "Key file #{local_authority_name}.key not found" unless key_file
85
- @private_key = OpenSSL::PKey::RSA.new(File.read(key_file))
86
- raise ConfigurationError, "Expected #{key_file} to contain an RSA private key" unless @private_key.private?
66
+ # Propagate a deprecated parameter
67
+ # @deprecated remove for v4.0
68
+ if keystore_directory.is_a?(String)
69
+ all_files = Dir.glob(File.join(keystore_directory, '*'))
70
+ public_keys = all_files.select { |kf| kf =~ /\.pub$/ }
71
+ raise ConfigurationError, "No public keys (*.pub) found in #{keystore_directory}" if public_keys.empty?
72
+
73
+ @configuration['common'] ||= {}
74
+ @configuration['common']['keystore'] ||= {}
75
+ @configuration['common']['keystore']['public'] = [keystore_directory]
76
+
77
+ # Propagate a deprecated configuration option
78
+ # @deprecated remove for v4.0
79
+ if (private_key = @configuration['authority'])
80
+ key_file = all_files.detect { |kf| kf =~ /#{private_key}\.key$/ }
81
+ raise ConfigurationError, "Key file #{private_key}.key not found in #{keystore_directory}" unless key_file
82
+ @configuration['common'] ||= {}
83
+ @configuration['common']['keystore'] ||= {}
84
+ @configuration['common']['keystore']['private'] = key_file
85
+ end
87
86
  end
88
87
 
88
+ @keystore = Keystore.new(configuration)
89
89
  @invalid_sessions = Set.new
90
90
  end
91
91
 
@@ -95,6 +95,7 @@ module GlobalSession
95
95
  # DEPRECATED: If a cookie is provided, load an existing session from its
96
96
  # serialized form. You should use #load_session for this instead.
97
97
  #
98
+ # @deprecated will be removed in GlobalSession v4; please use #load_session instead
98
99
  # @see load_session
99
100
  #
100
101
  # === Parameters
@@ -146,12 +147,33 @@ module GlobalSession
146
147
  Session.new(self, cookie)
147
148
  end
148
149
 
150
+ # @return [Hash] map of String authority-names to OpenSSL::PKey public-keys
151
+ # @deprecated will be removed in GlobalSession v4; please use Keystore instead
152
+ # @see GlobalSession::Keystore
153
+ def authorities
154
+ @keystore.public_keys
155
+ end
156
+
157
+ # Determine the private key associated with this directory, to be used for signing.
158
+ #
159
+ # @return [nil,OpenSSL::PKey] local authority key if we are an authority, else nil
160
+ # @deprecated will be removed in GlobalSession v4; please use Keystore instead
161
+ # @see GlobalSession::Keystore
162
+ def private_key
163
+ @keystore.private_key || @private_key
164
+ end
165
+
166
+ # Determine the authority name associated with this directory's private session-signing key.
167
+ #
168
+ # @deprecated will be removed in GlobalSession v4; please use Keystore instead
169
+ # @see GlobalSession::Keystore
149
170
  def local_authority_name
150
- @configuration['authority']
171
+ @keystore.private_key_name || @private_key_name
151
172
  end
152
173
 
153
- # Determine whether this system trusts a particular authority based on
154
- # the trust settings specified in Configuration.
174
+ # Determine whether this system trusts a particular named authority based on
175
+ # the settings specified in Configuration and/or the presence of public key
176
+ # files on disk.
155
177
  #
156
178
  # === Parameters
157
179
  # authority(String):: The name of the authority
@@ -159,7 +181,13 @@ module GlobalSession
159
181
  # === Return
160
182
  # trusted(true|false):: whether the local system trusts sessions signed by the specified authority
161
183
  def trusted_authority?(authority)
162
- @configuration['trust'].include?(authority)
184
+ if @configuration.has_key?('trust')
185
+ # Explicit trust in just the authorities specified in the configuration
186
+ @configuration['trust'].include?(authority)
187
+ else
188
+ # Implicit trust in any public key we found on disk
189
+ @keystore.public_keys.keys.include?(authority)
190
+ end
163
191
  end
164
192
 
165
193
  # Determine whether the given session UUID is valid. The default implementation only considers
@@ -190,5 +218,5 @@ module GlobalSession
190
218
  def report_invalid_session(uuid, expired_at)
191
219
  @invalid_sessions << uuid
192
220
  end
193
- end
221
+ end
194
222
  end
@@ -0,0 +1,139 @@
1
+ # Copyright (c) 2014- RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'set'
23
+ require 'uri'
24
+
25
+ module GlobalSession
26
+ # Keystore uses one or more filesystem directories as a backing store
27
+ # for RSA keys of global session authorities. The directories should
28
+ # contain one or more +*.pub+ files containing OpenSSH-format public
29
+ # RSA keys. The name of the pub file determines the name of the
30
+ # authority it represents.
31
+ #
32
+ # === The Local Authority
33
+ # Directory will infer the name of the local authority (if any) by
34
+ # looking for a private-key file in the keystore. If a +*.key+ file
35
+ # is found, then its name is taken to be the name of the local
36
+ # authority and all GlobalSessions created will be signed by that
37
+ # authority's private key.
38
+ #
39
+ # If more than one private key file is found, Directory will raise
40
+ # an error at initialization time.
41
+ #
42
+ class Keystore
43
+ # @return [Configuration] shared configuration object
44
+ attr_reader :configuration
45
+
46
+ # @return [Hash] map of String authority-names to OpenSSL::PKey public-keys
47
+ attr_reader :public_keys
48
+
49
+ # @return [nil, String] name of local authority if we are one, else nil
50
+ attr_reader :private_key_name
51
+
52
+ # @return [nil,OpenSSL::PKey] local authority key if we are an authority, else nil
53
+ attr_reader :private_key
54
+
55
+ # @return a representation of the object suitable for printing to the console
56
+ def inspect
57
+ "<#{self.class.name} @configuration=#{@configuration.inspect}>"
58
+ end
59
+
60
+ def initialize(configuration)
61
+ @configuration = configuration
62
+ load
63
+ end
64
+
65
+ private
66
+
67
+ # Load all public and/or private keys from location(s) specified in the configuration's
68
+ # "keystore/public" and "keystore/private" directives.
69
+ #
70
+ # @raise [ConfigurationError] if some authority's public key has already been loaded
71
+ def load
72
+ locations = Array((configuration['keystore'] || {})['public'] || [])
73
+
74
+ locations.each do |location|
75
+ load_public_key(location)
76
+ end
77
+
78
+ location = (configuration['keystore'] || {})['private']
79
+ location ||= ENV['GLOBAL_SESSION_PRIVATE_KEY']
80
+ load_private_key(location) if location # then we must be an authority; load our key
81
+ end
82
+
83
+ # Load a single authority's public key, or an entire directory full of public keys. Assume
84
+ # that the basenames of the key files are the authority names, e.g. "dev.pub" --> "dev".
85
+ #
86
+ # @param [String] path to file or directory to load
87
+ # @raise [Errno::ENOENT] if path is neither a file nor a directory
88
+ # @raise [ConfigurationError] if some authority's public key has already been loaded
89
+ def load_public_key(path)
90
+ @public_keys ||= {}
91
+
92
+ if File.directory?(path)
93
+ Dir.glob(File.join(path, '*')).each do |file|
94
+ load_public_key(file)
95
+ end
96
+ elsif File.file?(path)
97
+ name = File.basename(path, '.*')
98
+ key = OpenSSL::PKey::RSA.new(File.read(path))
99
+ # ignore private keys (which legacy config allowed to coexist with public keys)
100
+ unless key.private?
101
+ if @public_keys.has_key?(name)
102
+ raise ConfigurationError, "Duplicate public key for authority: #{name}"
103
+ else
104
+ @public_keys[name] = key
105
+ end
106
+ end
107
+ else
108
+ raise Errno::ENOENT.new("Path is neither a file nor a directory: " + path)
109
+ end
110
+ end
111
+
112
+ # Load a private key. Assume that the basename of the key file is the local authority name,
113
+ # e.g. "dev.key" --> "dev".
114
+ #
115
+ # @param [String] path to private-key file
116
+ # @raise [Errno::ENOENT] if path is not a file
117
+ # @raise [ConfigurationError] if some private key has already been loaded
118
+ def load_private_key(path)
119
+ if File.directory?(path)
120
+ # Arbitrarily pick the first key file found in the directory
121
+ path = Dir.glob(File.join(path, '*.key')).first
122
+ end
123
+
124
+ if File.file?(path)
125
+ if @private_key.nil?
126
+ name = File.basename(path, '.*')
127
+ private_key = OpenSSL::PKey::RSA.new(File.read(path))
128
+ raise ConfigurationError, "Expected #{key_file} to contain an RSA private key" unless private_key.private?
129
+ @private_key = private_key
130
+ @private_key_name = name
131
+ else
132
+ raise ConfigurationError, "Only one private key is allowed; already loaded #{@private_key_name}, cannot also load #{path}"
133
+ end
134
+ else
135
+ raise Errno::ENOENT.new("Path is not a file: " + path)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -29,61 +29,84 @@ module GlobalSession
29
29
  class Middleware
30
30
  LOCAL_SESSION_KEY = "rack.session".freeze
31
31
 
32
- # Make a new global session.
32
+ # @return [GlobalSession::Configuration]
33
+ attr_accessor :configuration
34
+
35
+ # @return [GlobalSession::Directory]
36
+ attr_accessor :directory
37
+
38
+ # Make a new global session middleware.
33
39
  #
34
40
  # The optional block here controls an alternate ticket retrieval
35
41
  # method. If no ticket is stored in the cookie jar, this
36
42
  # function is called. If it returns a non-nil value, that value
37
43
  # is the ticket.
38
44
  #
39
- # === Parameters
40
- # app(Rack client): application to run
41
- # configuration(String or Configuration): global_session configuration.
42
- # If a string, is interpreted as a
43
- # filename to load the config from.
44
- # directory(String or Directory): Directory object that provides
45
- # trust services to the global
46
- # session implementation. If a
47
- # string, is interpreted as a
48
- # filesystem directory containing
49
- # the public and private keys of
50
- # authorities, from which default
51
- # trust services will be initialized.
45
+ # @param [Configuration] configuration
46
+ # @param optional [String,Directory] directory the directory class name (DEPRECATED) or an actual instance of Directory
52
47
  #
53
- # block: optional alternate ticket retrieval function
54
- def initialize(app, configuration, directory, &block)
48
+ # @yield if a block is provided, yields to the block to fetch session data from request state
49
+ # @yieldparam [Hash] env Rack request environment is passed as a yield parameter
50
+ def initialize(app, configuration, directory=nil, &block)
55
51
  @app = app
56
52
 
53
+ # Initialize shared configuration
54
+ # @deprecated require Configuration object in v4
57
55
  if configuration.instance_of?(String)
58
56
  @configuration = Configuration.new(configuration, ENV['RACK_ENV'] || 'development')
59
57
  else
60
58
  @configuration = configuration
61
59
  end
62
60
 
61
+ klass = nil
63
62
  begin
64
- klass_name = @configuration['directory'] || 'GlobalSession::Directory'
63
+ # v0.9.0 - v3.0.4: class name is the value of the 'directory' key
64
+ klass_name = @configuration['directory']
65
+
66
+ case klass_name
67
+ when Hash
68
+ # v3.0.5 and beyond: class name is in 'class' subkey
69
+ klass_name = klass_name['class']
70
+ when NilClass
71
+ # the eternal default, if the class name is not provided
72
+ klass_name = 'GlobalSession::Directory'
73
+ end
65
74
 
66
- #Constantize the type name that was given as a string
67
- parts = klass_name.split('::')
68
- namespace = Object
69
- namespace = namespace.const_get(parts.shift.to_sym) until parts.empty?
70
- directory_klass = namespace
75
+ if klass_name.is_a?(String)
76
+ # for apps
77
+ klass = klass_name.to_const
78
+ else
79
+ # for specs that need to directly inject a class/object
80
+ klass = klass_name
81
+ end
71
82
  rescue Exception => e
72
- raise GlobalSession::ConfigurationError, "Invalid/unknown directory class name #{@configuration['directory']}"
83
+ raise GlobalSession::ConfigurationError,
84
+ "Invalid/unknown directory class name: #{klass_name.inspect}"
73
85
  end
74
86
 
75
- if directory.instance_of?(String)
76
- @directory = directory_klass.new(@configuration, directory)
77
- else
87
+ # Initialize the directory
88
+ # @deprecated require Directory object in v4
89
+ if klass.is_a?(Class)
90
+ @directory = klass.new(@configuration, directory)
91
+ elsif klass.is_a?(Directory)
78
92
  @directory = directory
93
+ else
94
+ raise GlobalSession::ConfigurationError,
95
+ "Unsupported value for 'directory': expected Class or Directory, got #{klass.inspect}"
79
96
  end
80
97
 
98
+ # Initialize the keystore
99
+ @keystore = Keystore.new(@configuration)
100
+
81
101
  @cookie_retrieval = block
82
- @cookie_name = @configuration['cookie']['name']
102
+ @cookie_name = @configuration['cookie']['name']
83
103
  end
84
104
 
85
105
  # Rack request chain. Sets up the global session ticket from
86
106
  # the environment and passes it up the chain.
107
+ #
108
+ # @return [Array] valid Rack response tuple e.g. [200, 'hello world']
109
+ # @param [Hash] env Rack request environment
87
110
  def call(env)
88
111
  env['rack.cookies'] = {} unless env['rack.cookies']
89
112
 
@@ -121,11 +144,8 @@ module GlobalSession
121
144
  # header was found, also disable global session cookie update and renewal by setting the
122
145
  # corresponding keys of the Rack environment.
123
146
  #
124
- # === Parameters
125
- # env(Hash): Rack environment.
126
- #
127
- # === Return
128
- # result(true,false):: Returns true if the environment was populated, false otherwise
147
+ # @return [Boolean] true if the environment was populated, false otherwise
148
+ # @param [Hash] env Rack request environment
129
149
  def read_authorization_header(env)
130
150
  if env.has_key? 'X-HTTP_AUTHORIZATION'
131
151
  # RFC2617 style (preferred by OAuth 2.0 spec)
@@ -138,9 +158,9 @@ module GlobalSession
138
158
  end
139
159
 
140
160
  if header_data && header_data.size == 2 && header_data.first.downcase == 'bearer'
141
- env['global_session.req.renew'] = false
161
+ env['global_session.req.renew'] = false
142
162
  env['global_session.req.update'] = false
143
- env['global_session'] = @directory.load_session(header_data.last)
163
+ env['global_session'] = @directory.load_session(header_data.last)
144
164
  true
145
165
  else
146
166
  false
@@ -149,11 +169,8 @@ module GlobalSession
149
169
 
150
170
  # Read a global session from HTTP cookies, if present.
151
171
  #
152
- # === Parameters
153
- # env(Hash): Rack environment.
154
- #
155
- # === Return
156
- # result(true,false):: Returns true if the environment was populated, false otherwise
172
+ # @return [Boolean] true if the environment was populated, false otherwise
173
+ # @param [Hash] env Rack request environment
157
174
  def read_cookie(env)
158
175
  if @cookie_retrieval && (cookie = @cookie_retrieval.call(env))
159
176
  env['global_session'] = @directory.load_session(cookie)
@@ -169,11 +186,8 @@ module GlobalSession
169
186
  # Ensure that the Rack environment contains a global session object; create a session
170
187
  # if necessary.
171
188
  #
172
- # === Parameters
173
- # env(Hash): Rack environment.
174
- #
175
- # === Return
176
- # true:: always returns true
189
+ # @return [true] always returns true
190
+ # @param [Hash] env Rack request environment
177
191
  def create_session(env)
178
192
  env['global_session'] ||= @directory.create_session
179
193
 
@@ -182,49 +196,53 @@ module GlobalSession
182
196
 
183
197
  # Renew the session ticket.
184
198
  #
185
- # === Parameters
186
- # env(Hash): Rack environment
199
+ # @return [true] always returns true
200
+ # @param [Hash] env Rack request environment
187
201
  def renew_cookie(env)
188
- return unless @directory.local_authority_name
202
+ return unless @configuration['authority']
189
203
  return if env['global_session.req.renew'] == false
190
204
 
191
205
  if (renew = @configuration['renew']) && env['global_session'] &&
192
- env['global_session'].expired_at < Time.at(Time.now.utc + 60 * renew.to_i)
206
+ env['global_session'].expired_at < Time.at(Time.now.utc + 60 * renew.to_i)
193
207
  env['global_session'].renew!
194
208
  end
209
+
210
+ true
195
211
  end
196
212
 
197
213
  # Update the cookie jar with the revised ticket.
198
214
  #
199
- # === Parameters
200
- # env(Hash): Rack environment
215
+ # @return [true] always returns true
216
+ # @param [Hash] env Rack request environment
201
217
  def update_cookie(env)
202
- return unless @directory.local_authority_name
203
- return if env['global_session.req.update'] == false
218
+ return true unless @configuration['authority']
219
+ return true if env['global_session.req.update'] == false
204
220
 
205
221
  session = env['global_session']
206
222
 
207
223
  if session
208
224
  unless session.valid?
209
225
  old_session = session
210
- session = @directory.create_session
226
+ session = @directory.create_session
211
227
  perform_invalidation_callbacks(env, old_session, session)
212
228
  env['global_session'] = session
213
229
  end
214
230
 
215
- value = session.to_s
231
+ value = session.to_s
216
232
  expires = @configuration['ephemeral'] ? nil : session.expired_at
217
233
  unless env['rack.cookies'][@cookie_name] == value
218
234
  env['rack.cookies'][@cookie_name] =
219
- {:value => value,
220
- :domain => cookie_domain(env),
221
- :expires => expires,
222
- :httponly=>true}
235
+ {:value => value,
236
+ :domain => cookie_domain(env),
237
+ :expires => expires,
238
+ :httponly => true}
223
239
  end
224
240
  else
225
241
  # write an empty cookie
226
242
  wipe_cookie(env)
227
243
  end
244
+
245
+ true
228
246
  rescue Exception => e
229
247
  wipe_cookie(env)
230
248
  raise e
@@ -232,25 +250,27 @@ module GlobalSession
232
250
 
233
251
  # Delete the global session cookie from the cookie jar.
234
252
  #
235
- # === Parameters
236
- # env(Hash): Rack environment
253
+ # @return [true] always returns true
254
+ # @param [Hash] env Rack request environment
237
255
  def wipe_cookie(env)
238
- return unless @directory.local_authority_name
256
+ return unless @configuration['authority']
239
257
  return if env['global_session.req.update'] == false
240
258
 
241
- env['rack.cookies'][@cookie_name] = {:value => nil,
242
- :domain => cookie_domain(env),
259
+ env['rack.cookies'][@cookie_name] = {:value => nil,
260
+ :domain => cookie_domain(env),
243
261
  :expires => Time.at(0)}
262
+
263
+ true
244
264
  end
245
265
 
246
266
  # Handle exceptions that occur during app invocation. This will either save the error
247
267
  # in the Rack environment or raise it, depending on the type of error. The error may
248
268
  # also be logged.
249
269
  #
250
- # === Parameters
251
- # activity(String): name of activity in which error happened
252
- # env(Hash): Rack environment
253
- # e(Exception): error that happened
270
+ # @return [true] always returns true
271
+ # @param [String] activity name of activity during which the error happened
272
+ # @param [Hash] env Rack request environment
273
+ # @param [Exception] e error that happened
254
274
  def handle_error(activity, env, e)
255
275
  if env['rack.logger']
256
276
  msg = "#{e.class} while #{activity}: #{e}"
@@ -264,17 +284,20 @@ module GlobalSession
264
284
  elsif e.is_a? ConfigurationError
265
285
  env['global_session.error'] = e
266
286
  else
287
+ # Don't intercept errors unless they're GlobalSession-related
267
288
  raise e
268
289
  end
290
+
291
+ true
269
292
  end
270
293
 
271
294
  # Perform callbacks to directory and/or local session
272
295
  # informing them that this session has been invalidated.
273
296
  #
274
- # === Parameters
275
- # env(Hash):: the rack environment
276
- # old_session(GlobalSession):: the now-invalidated session
277
- # new_session(GlobalSession):: the new session that will be sent to the client
297
+ # @return [true] always returns true
298
+ # @param [Hash] env Rack request environment
299
+ # @param [GlobalSession::Session] old_session now-invalidated session
300
+ # @param [GlobalSession::Session] new_session new session that will be sent to the client
278
301
  def perform_invalidation_callbacks(env, old_session, new_session)
279
302
  if (local_session = env[LOCAL_SESSION_KEY]) && local_session.respond_to?(:rename!)
280
303
  local_session.rename!(old_session, new_session)
@@ -287,16 +310,15 @@ module GlobalSession
287
310
  # in the configuration if one is found; otherwise, uses the SERVER_NAME from the request
288
311
  # but strips off the first component if the domain name contains more than two components.
289
312
  #
290
- # === Parameters
291
- # env(Hash):: the Rack environment hash
313
+ # @param [Hash] env Rack request environment
292
314
  def cookie_domain(env)
293
- if @configuration['cookie'].key?('domain')
315
+ if @configuration['cookie'].has_key?('domain')
294
316
  # Use the explicitly provided domain name
295
317
  domain = @configuration['cookie']['domain']
296
318
  else
297
319
  # Use the server name, but strip off the most specific component
298
- parts = env['SERVER_NAME'].split('.')
299
- parts = parts[1..-1] if parts.length > 2
320
+ parts = env['SERVER_NAME'].split('.')
321
+ parts = parts[1..-1] if parts.length > 2
300
322
  domain = parts.join('.')
301
323
  end
302
324