global_session 3.0.5 → 3.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.
@@ -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