global_session 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,162 @@
|
|
1
|
+
basedir = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
# Make sure the namespace exists, to satisfy Rails auto-loading
|
4
|
+
module GlobalSession
|
5
|
+
module Rack
|
6
|
+
# Global session middleware. Note: this class relies on
|
7
|
+
# Rack::Cookies being used higher up in the chain.
|
8
|
+
class Middleware
|
9
|
+
# Make a new global session.
|
10
|
+
#
|
11
|
+
# The optional block here controls an alternate ticket retrieval
|
12
|
+
# method. If no ticket is stored in the cookie jar, this
|
13
|
+
# function is called. If it returns a non-nil value, that value
|
14
|
+
# is the ticket.
|
15
|
+
#
|
16
|
+
# === Parameters
|
17
|
+
# app(Rack client): application to run
|
18
|
+
# configuration(String or Configuration): global_session configuration.
|
19
|
+
# If a string, is interpreted as a
|
20
|
+
# filename to load the config from.
|
21
|
+
# directory(String or Directory): Directory object that provides
|
22
|
+
# trust services to the global
|
23
|
+
# session implementation. If a
|
24
|
+
# string, is interpreted as a
|
25
|
+
# filesystem directory containing
|
26
|
+
# the public and private keys of
|
27
|
+
# authorities, from which default
|
28
|
+
# trust services will be initialized.
|
29
|
+
#
|
30
|
+
# block: optional alternate ticket retrieval function
|
31
|
+
def initialize(app, configuration, directory, &block)
|
32
|
+
@app = app
|
33
|
+
|
34
|
+
if configuration.instance_of?(String)
|
35
|
+
@configuration = Configuration.new(configuration, ENV['RACK_ENV'] || 'development')
|
36
|
+
else
|
37
|
+
@configuration = configuration
|
38
|
+
end
|
39
|
+
|
40
|
+
if directory.instance_of?(String)
|
41
|
+
@directory = Directory.new(@configuration, directory)
|
42
|
+
else
|
43
|
+
@directory = directory
|
44
|
+
end
|
45
|
+
|
46
|
+
@cookie_retrieval = block
|
47
|
+
@cookie_name = @configuration['cookie']['name']
|
48
|
+
end
|
49
|
+
|
50
|
+
# Read a cookie from the Rack environment.
|
51
|
+
#
|
52
|
+
# === Parameters
|
53
|
+
# env(Hash): Rack environment.
|
54
|
+
def read_cookie(env)
|
55
|
+
if env['rack.cookies'].key?(@cookie_name)
|
56
|
+
env['global_session'] = Session.new(@directory,
|
57
|
+
env['rack.cookies'][@cookie_name])
|
58
|
+
elsif @cookie_retrieval && cookie = @cookie_retrieval.call(env)
|
59
|
+
env['global_session'] = Session.new(@directory, cookie)
|
60
|
+
else
|
61
|
+
env['global_session'] = Session.new(@directory)
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
# Renew the session ticket.
|
68
|
+
#
|
69
|
+
# === Parameters
|
70
|
+
# env(Hash): Rack environment
|
71
|
+
def renew_cookie(env)
|
72
|
+
if (renew = @configuration['renew']) && env['global_session'] &&
|
73
|
+
env['global_session.req.renew'] != false &&
|
74
|
+
env['global_session'].directory.local_authority_name &&
|
75
|
+
env['global_session'].expired_at < Time.at(Time.now.utc + 60 * renew.to_i)
|
76
|
+
env['global_session'].renew!
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Update the cookie jar with the revised ticket.
|
81
|
+
#
|
82
|
+
# === Parameters
|
83
|
+
# env(Hash): Rack environment
|
84
|
+
def update_cookie(env)
|
85
|
+
return if env['global_session.req.update'] == false
|
86
|
+
|
87
|
+
begin
|
88
|
+
domain = @configuration['cookie']['domain'] || env['SERVER_NAME']
|
89
|
+
if env['global_session'] && env['global_session'].valid?
|
90
|
+
value = env['global_session'].to_s
|
91
|
+
expires = @configuration['ephemeral'] ? nil : env['global_session'].expired_at
|
92
|
+
unless env['rack.cookies'].key?(@cookie_name) &&
|
93
|
+
env['rack.cookies'][@cookie_name] == value
|
94
|
+
env['rack.cookies'][@cookie_name] = {:value => value, :domain => domain, :expires => expires}
|
95
|
+
end
|
96
|
+
else
|
97
|
+
# write an empty cookie
|
98
|
+
env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
|
99
|
+
end
|
100
|
+
rescue Exception => e
|
101
|
+
wipe_cookie(env)
|
102
|
+
raise e
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Delete the ticket from the cookie jar.
|
107
|
+
#
|
108
|
+
# === Parameters
|
109
|
+
# env(Hash): Rack environment
|
110
|
+
def wipe_cookie(env)
|
111
|
+
domain = @configuration['cookie']['domain'] || env['SERVER_NAME']
|
112
|
+
env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Handle exceptions that occur during app invocation.
|
116
|
+
#
|
117
|
+
# === Parameters
|
118
|
+
# activity(String): name of activity in which error happened
|
119
|
+
# env(Hash): Rack environment
|
120
|
+
# e(Exception): error that happened
|
121
|
+
def handle_error(activity, env, e)
|
122
|
+
if e.is_a? ClientError
|
123
|
+
env['global_session.error'] = e
|
124
|
+
return @app.call(env)
|
125
|
+
elsif e.is_a? ConfigurationError
|
126
|
+
env['rack.logger'].error("#{e.class} while #{activity}: #{e} #{e.backtrace}") if env['rack.logger']
|
127
|
+
env['global_session.error'] = e
|
128
|
+
return @app.call(env)
|
129
|
+
else
|
130
|
+
raise e
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Rack request chain. Sets up the global session ticket from
|
135
|
+
# the environment and passes it up the chain.
|
136
|
+
def call(env)
|
137
|
+
env['rack.cookies'] = {} unless env['rack.cookies']
|
138
|
+
|
139
|
+
begin
|
140
|
+
read_cookie(env)
|
141
|
+
rescue Exception => e
|
142
|
+
env['global_session'] = Session.new(@directory)
|
143
|
+
return handle_error('reading session cookie', env, e)
|
144
|
+
end
|
145
|
+
|
146
|
+
begin
|
147
|
+
tuple = @app.call(env)
|
148
|
+
renew_cookie(env)
|
149
|
+
update_cookie(env)
|
150
|
+
return tuple
|
151
|
+
rescue Exception => e
|
152
|
+
wipe_cookie(env)
|
153
|
+
return handle_error('processing request', env, e)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
module Rack
|
161
|
+
GlobalSession = GlobalSession::Rack::Middleware unless defined?(GlobalSession)
|
162
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module GlobalSession
|
2
|
+
module Rails
|
3
|
+
# Module that is mixed into ActionController's eigenclass; provides access to shared
|
4
|
+
# app-wide data such as the configuration object.
|
5
|
+
module ActionControllerClassMethods
|
6
|
+
def global_session_config
|
7
|
+
unless @global_session_config
|
8
|
+
config_file = File.join(::Rails.root, 'config', 'global_session.yml')
|
9
|
+
@global_session_config = GlobalSession::Configuration.new(config_file, RAILS_ENV)
|
10
|
+
end
|
11
|
+
|
12
|
+
return @global_session_config
|
13
|
+
end
|
14
|
+
|
15
|
+
def global_session_config=(config)
|
16
|
+
@global_session_config = config
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_global_session(options={})
|
20
|
+
odefault = {:integrated=>false}
|
21
|
+
obase = self.superclass.global_session_options if self.superclass.respond_to?(:global_session_options)
|
22
|
+
options = odefault.merge(obase).merge(options)
|
23
|
+
|
24
|
+
self.global_session_options = HashWithIndifferentAccess.new(options)
|
25
|
+
options = self.global_session_options
|
26
|
+
|
27
|
+
include GlobalSession::Rails::ActionControllerInstanceMethods
|
28
|
+
|
29
|
+
fopt = {}
|
30
|
+
inverse_fopt = {}
|
31
|
+
fopt[:only] = options[:only] if options[:only]
|
32
|
+
fopt[:except] = options[:except] if options[:except]
|
33
|
+
inverse_fopt[:only] = options[:except] if options[:except]
|
34
|
+
inverse_fopt[:except] = options[:only] if options[:only]
|
35
|
+
|
36
|
+
if fopt[:only] || fopt[:except]
|
37
|
+
before_filter :global_session_skip_renew, inverse_fopt
|
38
|
+
before_filter :global_session_skip_update, inverse_fopt
|
39
|
+
end
|
40
|
+
|
41
|
+
before_filter :global_session_initialize, fopt
|
42
|
+
end
|
43
|
+
|
44
|
+
def no_global_session
|
45
|
+
skip_before_filter :global_session_initialize
|
46
|
+
before_filter :global_session_skip_renew
|
47
|
+
before_filter :global_session_skip_update
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def global_session_options
|
53
|
+
@global_session_options || {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def global_session_options=(options)
|
57
|
+
@global_session_options = options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module GlobalSession
|
2
|
+
# Rails integration for GlobalSession.
|
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
|
+
#
|
12
|
+
module Rails
|
13
|
+
# Module that is mixed into ActionController-derived classes when the class method
|
14
|
+
# +has_global_session+ is called.
|
15
|
+
#
|
16
|
+
module ActionControllerInstanceMethods
|
17
|
+
def self.included(base) # :nodoc:
|
18
|
+
base.alias_method_chain :session, :global_session
|
19
|
+
end
|
20
|
+
|
21
|
+
# Shortcut accessor for global session configuration object.
|
22
|
+
#
|
23
|
+
# === Return
|
24
|
+
# config(GlobalSession::Configuration)
|
25
|
+
def global_session_config
|
26
|
+
request.env['global_session.config']
|
27
|
+
end
|
28
|
+
|
29
|
+
def global_session_options
|
30
|
+
self.class.instance_variable_get(:@global_session_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Global session reader.
|
34
|
+
#
|
35
|
+
# === Return
|
36
|
+
# session(Session):: the global session associated with the current request, nil if none
|
37
|
+
def global_session
|
38
|
+
@global_session
|
39
|
+
end
|
40
|
+
|
41
|
+
# Aliased version of ActionController::Base#session which will return the integrated
|
42
|
+
# global-and-local session object (IntegratedSession).
|
43
|
+
#
|
44
|
+
# === Return
|
45
|
+
# session(IntegratedSession):: the integrated session
|
46
|
+
def session_with_global_session
|
47
|
+
if global_session
|
48
|
+
unless @integrated_session &&
|
49
|
+
(@integrated_session.local == session_without_global_session) &&
|
50
|
+
(@integrated_session.global == global_session)
|
51
|
+
@integrated_session =
|
52
|
+
IntegratedSession.new(session_without_global_session, global_session)
|
53
|
+
end
|
54
|
+
|
55
|
+
return @integrated_session
|
56
|
+
else
|
57
|
+
return session_without_global_session
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Filter to initialize the global session.
|
62
|
+
#
|
63
|
+
# === Return
|
64
|
+
# true:: Always returns true
|
65
|
+
def global_session_initialize
|
66
|
+
options = self.class.instance_variable_get(:@global_session_options) || {}
|
67
|
+
|
68
|
+
if (error = request.env['global_session.error'])
|
69
|
+
raise error unless options[:raise] == false
|
70
|
+
else
|
71
|
+
@global_session = request.env['global_session']
|
72
|
+
return true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Filter to disable auto-renewal of the session.
|
77
|
+
#
|
78
|
+
# === Return
|
79
|
+
# true:: Always returns true
|
80
|
+
def global_session_skip_renew
|
81
|
+
request.env['global_session.req.renew'] = false
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
# Filter to disable updating of the session cookie
|
86
|
+
#
|
87
|
+
# === Return
|
88
|
+
# true:: Always returns true
|
89
|
+
def global_session_skip_update
|
90
|
+
request.env['global_session.req.update'] = false
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Override for the ActionController method of the same name that logs
|
95
|
+
# information about the request. Our version logs the global session ID
|
96
|
+
# instead of the local session ID.
|
97
|
+
#
|
98
|
+
# === Parameters
|
99
|
+
# name(Type):: Description
|
100
|
+
#
|
101
|
+
# === Return
|
102
|
+
# name(Type):: Description
|
103
|
+
def log_processing
|
104
|
+
if logger && logger.info?
|
105
|
+
log_processing_for_request_id
|
106
|
+
log_processing_for_parameters
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def log_processing_for_request_id # :nodoc:
|
111
|
+
if global_session && global_session.id
|
112
|
+
session_id = global_session.id + " (#{session[:session_id]})"
|
113
|
+
elsif session[:session_id]
|
114
|
+
session_id = session[:session_id]
|
115
|
+
elsif request.session_options[:id]
|
116
|
+
session_id = request.session_options[:id]
|
117
|
+
end
|
118
|
+
|
119
|
+
request_id = "\n\nProcessing #{self.class.name}\##{action_name} "
|
120
|
+
request_id << "to #{params[:format]} " if params[:format]
|
121
|
+
request_id << "(for #{request_origin.split[0]}) [#{request.method.to_s.upcase}]"
|
122
|
+
request_id << "\n Session ID: #{session_id}" if session_id
|
123
|
+
|
124
|
+
logger.info(request_id)
|
125
|
+
end
|
126
|
+
|
127
|
+
def log_processing_for_parameters # :nodoc:
|
128
|
+
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
|
129
|
+
parameters = parameters.except!(:controller, :action, :format, :_method)
|
130
|
+
|
131
|
+
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
basedir = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rack/contrib/cookies'
|
4
|
+
|
5
|
+
|
6
|
+
#Require the files necessary for Rails integration
|
7
|
+
require 'global_session/rack'
|
8
|
+
require 'global_session/rails/action_controller_class_methods'
|
9
|
+
require 'global_session/rails/action_controller_instance_methods'
|
10
|
+
|
11
|
+
module GlobalSession
|
12
|
+
module Rails
|
13
|
+
def self.activate(config)
|
14
|
+
# Enable ActionController integration.
|
15
|
+
class <<ActionController::Base
|
16
|
+
include GlobalSession::Rails::ActionControllerClassMethods
|
17
|
+
end
|
18
|
+
|
19
|
+
authorities = File.join(::Rails.root, 'config', 'authorities')
|
20
|
+
hgs_config = ActionController::Base.global_session_config
|
21
|
+
hgs_dir = GlobalSession::Directory.new(hgs_config, authorities)
|
22
|
+
|
23
|
+
# Add our middleware to the stack.
|
24
|
+
config.middleware.use ::Rack::Cookies
|
25
|
+
config.middleware.use ::Rack::GlobalSession, hgs_config, hgs_dir
|
26
|
+
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|