global_session 0.9.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/MIT-LICENSE +20 -0
- data/README.rdoc +179 -0
- data/global_session.gemspec +37 -0
- data/init.rb +4 -0
- data/lib/global_session/configuration.rb +126 -0
- data/lib/global_session/directory.rb +103 -0
- data/lib/global_session/encoding.rb +70 -0
- data/lib/global_session/integrated_session.rb +123 -0
- data/lib/global_session/rack.rb +162 -0
- data/lib/global_session/rails/action_controller_class_methods.rb +61 -0
- data/lib/global_session/rails/action_controller_instance_methods.rb +135 -0
- data/lib/global_session/rails.rb +30 -0
- data/lib/global_session/session.rb +343 -0
- data/lib/global_session.rb +63 -0
- data/rails/init.rb +3 -0
- data/rails_generators/global_session_authority/USAGE +1 -0
- data/rails_generators/global_session_authority/global_session_authority_generator.rb +32 -0
- data/rails_generators/global_session_config/USAGE +1 -0
- data/rails_generators/global_session_config/global_session_config_generator.rb +19 -0
- data/rails_generators/global_session_config/templates/global_session.yml.erb +49 -0
- metadata +214 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
# Standard library dependencies
|
2
|
+
require 'set'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
# Gem dependencies
|
6
|
+
require 'uuidtools'
|
7
|
+
|
8
|
+
module GlobalSession
|
9
|
+
# Ladies and gentlemen: the one and only, star of the show, GLOBAL SESSION!
|
10
|
+
#
|
11
|
+
# Session 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
|
+
#
|
16
|
+
class Session
|
17
|
+
attr_reader :id, :authority, :created_at, :expired_at, :directory
|
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
|
31
|
+
def initialize(directory, cookie=nil, valid_signature_digest=nil)
|
32
|
+
@configuration = directory.configuration
|
33
|
+
@schema_signed = Set.new((@configuration['attributes']['signed']))
|
34
|
+
@schema_insecure = Set.new((@configuration['attributes']['insecure']))
|
35
|
+
@directory = directory
|
36
|
+
|
37
|
+
if cookie && !cookie.empty?
|
38
|
+
load_from_cookie(cookie, valid_signature_digest)
|
39
|
+
elsif @directory.local_authority_name
|
40
|
+
create_from_scratch
|
41
|
+
else
|
42
|
+
create_invalid
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Determine whether the session is valid. This method simply delegates to the
|
47
|
+
# directory associated with this session.
|
48
|
+
#
|
49
|
+
# === Return
|
50
|
+
# valid(true|false):: True if the session is valid, false otherwise
|
51
|
+
def valid?
|
52
|
+
@directory.valid_session?(@id, @expired_at)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Serialize the session to a form suitable for use with HTTP cookies. If any
|
56
|
+
# secure attributes have changed since the session was instantiated, compute
|
57
|
+
# a fresh RSA signature.
|
58
|
+
#
|
59
|
+
# === Return
|
60
|
+
# cookie(String):: The B64cookie-encoded Zlib-compressed JSON-serialized global session hash
|
61
|
+
def to_s
|
62
|
+
if @cookie && !@dirty_insecure && !@dirty_secure
|
63
|
+
#use cached cookie if nothing has changed
|
64
|
+
return @cookie
|
65
|
+
end
|
66
|
+
|
67
|
+
hash = {'id'=>@id,
|
68
|
+
'tc'=>@created_at.to_i, 'te'=>@expired_at.to_i,
|
69
|
+
'ds'=>@signed}
|
70
|
+
|
71
|
+
if @signature && !@dirty_secure
|
72
|
+
#use cached signature unless we've changed secure state
|
73
|
+
authority = @authority
|
74
|
+
else
|
75
|
+
authority_check
|
76
|
+
authority = @directory.local_authority_name
|
77
|
+
hash['a'] = authority
|
78
|
+
digest = canonical_digest(hash)
|
79
|
+
@signature = Encoding::Base64Cookie.dump(@directory.private_key.private_encrypt(digest))
|
80
|
+
end
|
81
|
+
|
82
|
+
hash['dx'] = @insecure
|
83
|
+
hash['s'] = @signature
|
84
|
+
hash['a'] = authority
|
85
|
+
|
86
|
+
json = Encoding::JSON.dump(hash)
|
87
|
+
zbin = Zlib::Deflate.deflate(json, Zlib::BEST_COMPRESSION)
|
88
|
+
return Encoding::Base64Cookie.dump(zbin)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Determine whether the global session schema allows a given key to be placed
|
92
|
+
# in the global session.
|
93
|
+
#
|
94
|
+
# === Parameters
|
95
|
+
# key(String):: The name of the key
|
96
|
+
#
|
97
|
+
# === Return
|
98
|
+
# supported(true|false):: Whether the specified key is supported
|
99
|
+
def supports_key?(key)
|
100
|
+
@schema_signed.include?(key) || @schema_insecure.include?(key)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Determine whether this session contains a value with the specified key.
|
104
|
+
#
|
105
|
+
# === Parameters
|
106
|
+
# key(String):: The name of the key
|
107
|
+
#
|
108
|
+
# === Return
|
109
|
+
# contained(true|false):: Whether the session currently has a value for the specified key.
|
110
|
+
def has_key?(key)
|
111
|
+
@signed.has_key?(key) || @insecure.has_key?(key)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return the keys that are currently present in the global session.
|
115
|
+
#
|
116
|
+
# === Return
|
117
|
+
# keys(Array):: List of keys contained in the global session
|
118
|
+
def keys
|
119
|
+
@signed.keys + @insecure.keys
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return the values that are currently present in the global session.
|
123
|
+
#
|
124
|
+
# === Return
|
125
|
+
# values(Array):: List of values contained in the global session
|
126
|
+
def values
|
127
|
+
@signed.values + @insecure.values
|
128
|
+
end
|
129
|
+
|
130
|
+
# Iterate over each key/value pair
|
131
|
+
#
|
132
|
+
# === Block
|
133
|
+
# An iterator which will be called with each key/value pair
|
134
|
+
#
|
135
|
+
# === Return
|
136
|
+
# Returns the value of the last expression evaluated by the block
|
137
|
+
def each_pair(&block) # :yields: |key, value|
|
138
|
+
@signed.each_pair(&block)
|
139
|
+
@insecure.each_pair(&block)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Lookup a value by its key.
|
143
|
+
#
|
144
|
+
# === Parameters
|
145
|
+
# key(String):: the key
|
146
|
+
#
|
147
|
+
# === Return
|
148
|
+
# value(Object):: The value associated with +key+, or nil if +key+ is not present
|
149
|
+
def [](key)
|
150
|
+
key = key.to_s #take care of symbol-style keys
|
151
|
+
@signed[key] || @insecure[key]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Set a value in the global session hash. If the supplied key is denoted as
|
155
|
+
# secure by the global session schema, causes a new signature to be computed
|
156
|
+
# when the session is next serialized.
|
157
|
+
#
|
158
|
+
# === Parameters
|
159
|
+
# key(String):: The key to set
|
160
|
+
# value(Object):: The value to set
|
161
|
+
#
|
162
|
+
# === Return
|
163
|
+
# value(Object):: Always returns the value that was set
|
164
|
+
#
|
165
|
+
# ===Raise
|
166
|
+
# InvalidSession:: if the session has been invalidated (and therefore can't be written to)
|
167
|
+
# ArgumentError:: if the configuration doesn't define the specified key as part of the global session
|
168
|
+
# NoAuthority:: if the specified key is secure and the local node is not an authority
|
169
|
+
# UnserializableType:: if the specified value can't be serialized as JSON
|
170
|
+
def []=(key, value)
|
171
|
+
key = key.to_s #take care of symbol-style keys
|
172
|
+
raise InvalidSession unless valid?
|
173
|
+
|
174
|
+
#Ensure that the value is serializable (will raise if not)
|
175
|
+
canonicalize(value)
|
176
|
+
|
177
|
+
if @schema_signed.include?(key)
|
178
|
+
authority_check
|
179
|
+
@signed[key] = value
|
180
|
+
@dirty_secure = true
|
181
|
+
elsif @schema_insecure.include?(key)
|
182
|
+
@insecure[key] = value
|
183
|
+
@dirty_insecure = true
|
184
|
+
else
|
185
|
+
raise ArgumentError, "Attribute '#{key}' is not specified in global session configuration"
|
186
|
+
end
|
187
|
+
|
188
|
+
return value
|
189
|
+
end
|
190
|
+
|
191
|
+
# Invalidate this session by reporting its UUID to the Directory.
|
192
|
+
#
|
193
|
+
# === Return
|
194
|
+
# unknown(Object):: Returns whatever the Directory returns
|
195
|
+
def invalidate!
|
196
|
+
@directory.report_invalid_session(@id, @expired_at)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Renews this global session, changing its expiry timestamp into the future.
|
200
|
+
# Causes a new signature will be computed when the session is next serialized.
|
201
|
+
#
|
202
|
+
# === Return
|
203
|
+
# true:: Always returns true
|
204
|
+
def renew!(expired_at=nil)
|
205
|
+
authority_check
|
206
|
+
minutes = @configuration['timeout'].to_i
|
207
|
+
expired_at ||= Time.at(Time.now.utc + 60 * minutes)
|
208
|
+
@expired_at = expired_at
|
209
|
+
@dirty_secure = true
|
210
|
+
end
|
211
|
+
|
212
|
+
# Return the SHA1 hash of the most recently-computed RSA signature of this session.
|
213
|
+
# This isn't really intended for the end user; it exists so the Web framework integration
|
214
|
+
# code can optimize request speed by caching the most recently verified signature in the
|
215
|
+
# local session and avoid re-verifying it on every request.
|
216
|
+
#
|
217
|
+
# === Return
|
218
|
+
# digest(String):: SHA1 hex-digest of most-recently-computed signature
|
219
|
+
def signature_digest
|
220
|
+
@signature ? digest(@signature) : nil
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def authority_check # :nodoc:
|
226
|
+
unless @directory.local_authority_name
|
227
|
+
raise NoAuthority, 'Cannot change secure session attributes; we are not an authority'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def canonical_digest(input) # :nodoc:
|
232
|
+
canonical = Encoding::JSON.dump(canonicalize(input))
|
233
|
+
return digest(canonical)
|
234
|
+
end
|
235
|
+
|
236
|
+
def digest(input) # :nodoc:
|
237
|
+
return Digest::SHA1.new().update(input).hexdigest
|
238
|
+
end
|
239
|
+
|
240
|
+
def canonicalize(input) # :nodoc:
|
241
|
+
case input
|
242
|
+
when Hash
|
243
|
+
output = Array.new
|
244
|
+
ordered_keys = input.keys.sort
|
245
|
+
ordered_keys.each do |key|
|
246
|
+
output << [ canonicalize(key), canonicalize(input[key]) ]
|
247
|
+
end
|
248
|
+
when Array
|
249
|
+
output = input.collect { |x| canonicalize(x) }
|
250
|
+
when Numeric, String, NilClass
|
251
|
+
output = input
|
252
|
+
else
|
253
|
+
raise UnserializableType, "Objects of type #{input.class.name} cannot be serialized in the global session"
|
254
|
+
end
|
255
|
+
|
256
|
+
return output
|
257
|
+
end
|
258
|
+
|
259
|
+
def load_from_cookie(cookie, valid_signature_digest) # :nodoc:
|
260
|
+
begin
|
261
|
+
zbin = Encoding::Base64Cookie.load(cookie)
|
262
|
+
json = Zlib::Inflate.inflate(zbin)
|
263
|
+
hash = Encoding::JSON.load(json)
|
264
|
+
rescue Exception => e
|
265
|
+
mc = MalformedCookie.new("Caused by #{e.class.name}: #{e.message}")
|
266
|
+
mc.set_backtrace(e.backtrace)
|
267
|
+
raise mc
|
268
|
+
end
|
269
|
+
|
270
|
+
id = hash['id']
|
271
|
+
authority = hash['a']
|
272
|
+
created_at = Time.at(hash['tc'].to_i).utc
|
273
|
+
expired_at = Time.at(hash['te'].to_i).utc
|
274
|
+
signed = hash['ds']
|
275
|
+
insecure = hash.delete('dx')
|
276
|
+
signature = hash.delete('s')
|
277
|
+
|
278
|
+
unless valid_signature_digest == digest(signature)
|
279
|
+
#Check signature
|
280
|
+
expected = canonical_digest(hash)
|
281
|
+
signer = @directory.authorities[authority]
|
282
|
+
raise SecurityError, "Unknown signing authority #{authority}" unless signer
|
283
|
+
got = signer.public_decrypt(Encoding::Base64Cookie.load(signature))
|
284
|
+
unless (got == expected)
|
285
|
+
raise SecurityError, "Signature mismatch on global session cookie; tampering suspected"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
#Check trust in signing authority
|
290
|
+
unless @directory.trusted_authority?(authority)
|
291
|
+
raise SecurityError, "Global sessions signed by #{authority} are not trusted"
|
292
|
+
end
|
293
|
+
|
294
|
+
#Check expiration
|
295
|
+
unless expired_at > Time.now.utc
|
296
|
+
raise ExpiredSession, "Session expired at #{expired_at}"
|
297
|
+
end
|
298
|
+
|
299
|
+
#Check other validity (delegate to directory)
|
300
|
+
unless @directory.valid_session?(id, expired_at)
|
301
|
+
raise InvalidSession, "Global session has been invalidated"
|
302
|
+
end
|
303
|
+
|
304
|
+
#If all validation stuff passed, assign our instance variables.
|
305
|
+
@id = id
|
306
|
+
@authority = authority
|
307
|
+
@created_at = created_at
|
308
|
+
@expired_at = expired_at
|
309
|
+
@signed = signed
|
310
|
+
@insecure = insecure
|
311
|
+
@signature = signature
|
312
|
+
@cookie = cookie
|
313
|
+
end
|
314
|
+
|
315
|
+
def create_from_scratch # :nodoc:
|
316
|
+
authority_check
|
317
|
+
|
318
|
+
@signed = {}
|
319
|
+
@insecure = {}
|
320
|
+
@created_at = Time.now.utc
|
321
|
+
@authority = @directory.local_authority_name
|
322
|
+
|
323
|
+
if defined?(::UUIDTools) # UUIDTools v2
|
324
|
+
@id = ::UUIDTools::UUID.timestamp_create.to_s
|
325
|
+
elsif defined?(::UUID) # UUIDTools v1
|
326
|
+
@id = ::UUID.timestamp_create.to_s
|
327
|
+
else
|
328
|
+
raise TypeError, "Neither UUIDTools nor UUID defined; unsupported UUIDTools version?"
|
329
|
+
end
|
330
|
+
|
331
|
+
renew!
|
332
|
+
end
|
333
|
+
|
334
|
+
def create_invalid # :nodoc:
|
335
|
+
@id = nil
|
336
|
+
@created_at = Time.now.utc
|
337
|
+
@expired_at = created_at
|
338
|
+
@signed = {}
|
339
|
+
@insecure = {}
|
340
|
+
@authority = nil
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module GlobalSession
|
2
|
+
# Indicates that the global session configuration file is malformatted or missing
|
3
|
+
# required fields. Also used as a base class for other errors.
|
4
|
+
class ConfigurationError < Exception; end
|
5
|
+
|
6
|
+
# The general category of client-side errors. Used solely as a base class.
|
7
|
+
class ClientError < Exception; end
|
8
|
+
|
9
|
+
# Indicates that the global session configuration file is missing from disk.
|
10
|
+
#
|
11
|
+
class MissingConfiguration < ConfigurationError; end
|
12
|
+
|
13
|
+
# Indicates that a client submitted a request with a valid session cookie, but the
|
14
|
+
# session ID was reported as invalid by the Directory.
|
15
|
+
#
|
16
|
+
# See Directory#valid_session? for more information.
|
17
|
+
#
|
18
|
+
class InvalidSession < ClientError; end
|
19
|
+
|
20
|
+
# Indicates that a client submitted a request with a valid session cookie, but the
|
21
|
+
# session has expired.
|
22
|
+
#
|
23
|
+
class ExpiredSession < ClientError; end
|
24
|
+
|
25
|
+
# Indicates that a client submitted a request with a session cookie that could not
|
26
|
+
# be decoded or decompressed.
|
27
|
+
#
|
28
|
+
class MalformedCookie < ClientError; end
|
29
|
+
|
30
|
+
# Indicates that application code tried to put an unserializable object into the glboal
|
31
|
+
# session hash. Because the global session is serialized as JSON and not all Ruby types
|
32
|
+
# can be easily round-tripped to JSON and back without data loss, we constrain the types
|
33
|
+
# that can be serialized.
|
34
|
+
#
|
35
|
+
# See GlobalSession::Encoding::JSON for more information on serializable types.
|
36
|
+
#
|
37
|
+
class UnserializableType < ConfigurationError; end
|
38
|
+
|
39
|
+
# Indicates that the application code tried to write a secure session attribute or
|
40
|
+
# renew the global session. Both of these operations require a local authority
|
41
|
+
# because they require a new signature to be computed on the global session.
|
42
|
+
#
|
43
|
+
# See GlobalSession::Configuration and GlobalSession::Directory for more
|
44
|
+
# information.
|
45
|
+
#
|
46
|
+
class NoAuthority < ConfigurationError; end
|
47
|
+
end
|
48
|
+
|
49
|
+
#Make sure gem dependencies are activated.
|
50
|
+
require 'uuidtools'
|
51
|
+
require 'json'
|
52
|
+
require 'active_support'
|
53
|
+
|
54
|
+
#Require Ruby library dependencies
|
55
|
+
require 'openssl'
|
56
|
+
|
57
|
+
#Require the core suite of GlobalSession classes and modules
|
58
|
+
basedir = File.dirname(__FILE__)
|
59
|
+
require File.join(basedir, 'global_session', 'configuration')
|
60
|
+
require File.join(basedir, 'global_session', 'directory')
|
61
|
+
require File.join(basedir, 'global_session', 'encoding')
|
62
|
+
require File.join(basedir, 'global_session', 'session')
|
63
|
+
require File.join(basedir, 'global_session', 'integrated_session')
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
./script/generate global_session authority <name of authority>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class GlobalSessionAuthorityGenerator < Rails::Generator::Base
|
2
|
+
def initialize(runtime_args, runtime_options = {})
|
3
|
+
super
|
4
|
+
|
5
|
+
@app_name = File.basename(::Rails.root)
|
6
|
+
@auth_name = args.shift
|
7
|
+
raise ArgumentError, "Must specify name for global session authority, e.g. 'mycoolapp'" unless @auth_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def manifest
|
11
|
+
record do |m|
|
12
|
+
new_key = OpenSSL::PKey::RSA.generate( 1024 )
|
13
|
+
new_public = new_key.public_key.to_pem
|
14
|
+
new_private = new_key.to_pem
|
15
|
+
|
16
|
+
dest_dir = File.join(::Rails.root, 'config', 'authorities')
|
17
|
+
FileUtils.mkdir_p(dest_dir)
|
18
|
+
|
19
|
+
File.open(File.join(dest_dir, @auth_name + ".pub"), 'w') do |f|
|
20
|
+
f.puts new_public
|
21
|
+
end
|
22
|
+
|
23
|
+
File.open(File.join(dest_dir, @auth_name + ".key"), 'w') do |f|
|
24
|
+
f.puts new_private
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "***"
|
28
|
+
puts "*** Don't forget to delete config/authorities/#{@auth_name}.key"
|
29
|
+
puts "***"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
./script/generate global_session config <DNS domain for production cookie>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class GlobalSessionConfigGenerator < Rails::Generator::Base
|
2
|
+
def initialize(runtime_args, runtime_options = {})
|
3
|
+
super
|
4
|
+
|
5
|
+
@app_name = File.basename(::Rails.root)
|
6
|
+
@app_domain = args.shift
|
7
|
+
raise ArgumentError, "Must specify DNS domain for global session cookie, e.g. 'example.com'" unless @app_domain
|
8
|
+
end
|
9
|
+
|
10
|
+
def manifest
|
11
|
+
record do |m|
|
12
|
+
|
13
|
+
m.template 'global_session.yml.erb',
|
14
|
+
'config/global_session.yml',
|
15
|
+
:assigns=>{:app_name=>@app_name,
|
16
|
+
:app_domain=>@app_domain}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Common settings of the global session (that apply to all Rails environments)
|
2
|
+
# are listed here. These may be overidden in the environment-specific section.
|
3
|
+
common:
|
4
|
+
attributes:
|
5
|
+
# Signed attributes of the global session
|
6
|
+
signed:
|
7
|
+
- user
|
8
|
+
# Untrusted attributes of the global session
|
9
|
+
insecure:
|
10
|
+
- account
|
11
|
+
# Enable local session integration in order to use the ActionController
|
12
|
+
# method #session to access both local AND global session state, with
|
13
|
+
# global attributes always taking precedence over local attributes.
|
14
|
+
integrated: true
|
15
|
+
ephemeral: false
|
16
|
+
|
17
|
+
# Test/spec runs
|
18
|
+
test:
|
19
|
+
timeout: 15 #minutes
|
20
|
+
renew: 5 #minutes before expiration
|
21
|
+
cookie:
|
22
|
+
name: _session_gbl
|
23
|
+
#the name of the local authority (optional)
|
24
|
+
authority: test
|
25
|
+
#which authorities this app will trust
|
26
|
+
trust:
|
27
|
+
- test
|
28
|
+
|
29
|
+
# Development mode
|
30
|
+
development:
|
31
|
+
timeout: 60
|
32
|
+
renew: 15
|
33
|
+
cookie:
|
34
|
+
name: _session_gbl
|
35
|
+
authority: development
|
36
|
+
trust:
|
37
|
+
- development
|
38
|
+
- production
|
39
|
+
|
40
|
+
# Production mode
|
41
|
+
production:
|
42
|
+
timeout: 60
|
43
|
+
renew: 15
|
44
|
+
cookie:
|
45
|
+
name: _session_gbl
|
46
|
+
domain: <%= app_domain %>
|
47
|
+
authority: production
|
48
|
+
trust:
|
49
|
+
- production
|