global_session 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,12 +1,15 @@
1
1
  = Introduction
2
2
 
3
- HasGlobalSession enables multiple heterogeneous Web applications to share
3
+ GlobalSession enables multiple heterogeneous Web applications to share
4
4
  session state in a cryptographically secure way, facilitating single sign-on
5
- and enabling easier development of large-scale distributed applications that
5
+ and enabling easier development of distributed applications that
6
6
  make use of architectural strategies such as sharding or separation of concerns.
7
7
 
8
- In other words: it lets semi-related Web apps share selected bits of session
9
- state.
8
+ In other words: it glues your semi-related Web apps together so they share the
9
+ same bits of session state. This is done by putting the session itself into
10
+ cookies.
11
+
12
+ == What Is It Not?
10
13
 
11
14
  This plugin does not provide a complete solution for identity management. In
12
15
  particular, it does not provide any of the following:
@@ -73,7 +76,7 @@ whenever attributes change. As an optimization, the signature is only
73
76
  recomputed when the metadata or signed attributes have changed; insecure
74
77
  attributes can change "for free."
75
78
 
76
- Because the security properties of attributes can vary, HasGlobalSession
79
+ Because the security properties of attributes can vary, GlobalSession
77
80
  requires all _possible_ attributes to be declared up-front in the config
78
81
  file. The 'attributes' section of the config file defines the _schema_
79
82
  for the global session: which attributes can be used, which can be trusted
@@ -97,13 +100,13 @@ or they may be running different codebases that represent different parts of
97
100
  a distributed application. (They may also be using app frameworks other than
98
101
  Rails.)
99
102
 
100
- The only constraint imposed by HasGlobalSession is that all nodes within the
103
+ The only constraint imposed by GlobalSession is that all nodes within the
101
104
  domain must have end-user-facing URLs within the same second-level DNS domain.
102
105
  This is due to limitations imposed by the HTTP cookie mechanism: for privacy
103
106
  reasons, cookies will only be sent to nodes within the same domain as the
104
107
  node that first created them.
105
108
 
106
- For example, in my HasGlobalSession configuration file I might specify that my
109
+ For example, in my GlobalSession configuration file I might specify that my
107
110
  cookie's domain is "example.com". My app nodes at app1.example.com and
108
111
  app2.example.com would be part of the global session domain, but my business
109
112
  partner's application at app3.partner.com could not participate.
@@ -138,7 +141,7 @@ authorities:
138
141
 
139
142
  == The Directory
140
143
 
141
- The Directory is a Ruby object instantiated by HasGlobalSession in order to
144
+ The Directory is a Ruby object instantiated by GlobalSession in order to
142
145
  perform lookups of public and private keys. Given an authority name (as found
143
146
  in a session cookie), the Directory can find the corresponding public key.
144
147
 
@@ -146,7 +149,7 @@ If the local system is an authority itself, #local_authority_name will
146
149
  return non-nil and #private_key will return a private key suitable for
147
150
  signing session attributes.
148
151
 
149
- The Directory implementation included with HasGlobalSession uses the filesystem
152
+ The Directory implementation included with GlobalSession uses the filesystem
150
153
  as the backing store for its key pairs. Its #initialize method accepts a
151
154
  filesystem path that will be searched for files containing PEM-encoded public
152
155
  and private keys (the same format used by OpenSSH). This simple Directory
@@ -157,7 +160,7 @@ implementation relies on the following conventions:
157
160
  * The local node's authority name is inferred from the name of the private key
158
161
  file.
159
162
 
160
- When used with a Rails app, HasGlobalSession expects to find its keystore in
163
+ When used with a Rails app, GlobalSession expects to find its keystore in
161
164
  config/authorities. You can use the global_session generator to create new key
162
165
  pairs. Remember never to check a *.key file into a public repository!! (*.pub
163
166
  files can be checked into source control and distributed freely.)
@@ -169,7 +172,7 @@ its public key from config/authorities and re-deploy your app.
169
172
 
170
173
  To replace or enhance the built-in Directory, simply create a new class that
171
174
  extends Directory and put the class somewhere in your app (the lib directory
172
- is a good choice). In the HasGlobalSession configuration file, specify the
175
+ is a good choice). In the GlobalSession configuration file, specify the
173
176
  class name of the directory under the 'common' section, like so:
174
177
 
175
178
  common:
@@ -7,8 +7,8 @@ spec = Gem::Specification.new do |s|
7
7
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
8
 
9
9
  s.name = 'global_session'
10
- s.version = '0.9.0'
11
- s.date = '2010-12-07'
10
+ s.version = '1.0.0'
11
+ s.date = '2011-01-01'
12
12
 
13
13
  s.authors = ['Tony Spataro']
14
14
  s.email = 'code@tracker.xeger.net'
@@ -12,7 +12,7 @@ module GlobalSession
12
12
  # When using an integrated session, you can always get to the underlying objects by
13
13
  # using the #local and #global readers of this class.
14
14
  #
15
- class IntegratedSession
15
+ class GlobalSession::IntegratedSession
16
16
  # Return the local-session objects, whose type may vary depending on the Web framework.
17
17
  attr_reader :local
18
18
 
@@ -78,6 +78,8 @@ module GlobalSession
78
78
  @global.has_key?(key) || @local.has_key?(key)
79
79
  end
80
80
 
81
+ alias :key? :has_key?
82
+
81
83
  # Return the keys that are currently present in either the global or local session.
82
84
  #
83
85
  # === Return
@@ -52,7 +52,7 @@ module GlobalSession
52
52
  # === Parameters
53
53
  # env(Hash): Rack environment.
54
54
  def read_cookie(env)
55
- if env['rack.cookies'].key?(@cookie_name)
55
+ if env['rack.cookies'].has_key?(@cookie_name)
56
56
  env['global_session'] = Session.new(@directory,
57
57
  env['rack.cookies'][@cookie_name])
58
58
  elsif @cookie_retrieval && cookie = @cookie_retrieval.call(env)
@@ -69,9 +69,10 @@ module GlobalSession
69
69
  # === Parameters
70
70
  # env(Hash): Rack environment
71
71
  def renew_cookie(env)
72
+ return unless env['global_session'].directory.local_authority_name
73
+ return if env['global_session.req.renew'] == false
74
+
72
75
  if (renew = @configuration['renew']) && env['global_session'] &&
73
- env['global_session.req.renew'] != false &&
74
- env['global_session'].directory.local_authority_name &&
75
76
  env['global_session'].expired_at < Time.at(Time.now.utc + 60 * renew.to_i)
76
77
  env['global_session'].renew!
77
78
  end
@@ -82,6 +83,7 @@ module GlobalSession
82
83
  # === Parameters
83
84
  # env(Hash): Rack environment
84
85
  def update_cookie(env)
86
+ return unless env['global_session'].directory.local_authority_name
85
87
  return if env['global_session.req.update'] == false
86
88
 
87
89
  begin
@@ -89,7 +91,7 @@ module GlobalSession
89
91
  if env['global_session'] && env['global_session'].valid?
90
92
  value = env['global_session'].to_s
91
93
  expires = @configuration['ephemeral'] ? nil : env['global_session'].expired_at
92
- unless env['rack.cookies'].key?(@cookie_name) &&
94
+ unless env['rack.cookies'].has_key?(@cookie_name) &&
93
95
  env['rack.cookies'][@cookie_name] == value
94
96
  env['rack.cookies'][@cookie_name] = {:value => value, :domain => domain, :expires => expires}
95
97
  end
@@ -103,7 +105,7 @@ module GlobalSession
103
105
  end
104
106
  end
105
107
 
106
- # Delete the ticket from the cookie jar.
108
+ # Delete the global session cookie from the cookie jar.
107
109
  #
108
110
  # === Parameters
109
111
  # env(Hash): Rack environment
@@ -112,7 +114,9 @@ module GlobalSession
112
114
  env['rack.cookies'][@cookie_name] = {:value => nil, :domain => domain, :expires => Time.at(0)}
113
115
  end
114
116
 
115
- # Handle exceptions that occur during app invocation.
117
+ # Handle exceptions that occur during app invocation. This will either save the error
118
+ # in the Rack environment or raise it, depending on the type of error. The error may
119
+ # also be logged.
116
120
  #
117
121
  # === Parameters
118
122
  # activity(String): name of activity in which error happened
@@ -121,11 +125,10 @@ module GlobalSession
121
125
  def handle_error(activity, env, e)
122
126
  if e.is_a? ClientError
123
127
  env['global_session.error'] = e
124
- return @app.call(env)
128
+ wipe_cookie(env)
125
129
  elsif e.is_a? ConfigurationError
126
130
  env['rack.logger'].error("#{e.class} while #{activity}: #{e} #{e.backtrace}") if env['rack.logger']
127
131
  env['global_session.error'] = e
128
- return @app.call(env)
129
132
  else
130
133
  raise e
131
134
  end
@@ -140,17 +143,20 @@ module GlobalSession
140
143
  read_cookie(env)
141
144
  rescue Exception => e
142
145
  env['global_session'] = Session.new(@directory)
143
- return handle_error('reading session cookie', env, e)
146
+ handle_error('reading session cookie', env, e)
144
147
  end
145
148
 
149
+ tuple = nil
150
+
146
151
  begin
147
152
  tuple = @app.call(env)
153
+ rescue Exception => e
154
+ handle_error('processing request', env, e)
155
+ return tuple
156
+ else
148
157
  renew_cookie(env)
149
158
  update_cookie(env)
150
159
  return tuple
151
- rescue Exception => e
152
- wipe_cookie(env)
153
- return handle_error('processing request', env, e)
154
160
  end
155
161
  end
156
162
  end
@@ -158,5 +164,5 @@ module GlobalSession
158
164
  end
159
165
 
160
166
  module Rack
161
- GlobalSession = GlobalSession::Rack::Middleware unless defined?(GlobalSession)
167
+ GlobalSession = ::GlobalSession::Rack::Middleware unless defined?(::Rack::GlobalSession)
162
168
  end
@@ -1,7 +1,8 @@
1
1
  module GlobalSession
2
2
  module Rails
3
3
  # Module that is mixed into ActionController's eigenclass; provides access to shared
4
- # app-wide data such as the configuration object.
4
+ # app-wide data such as the configuration object, and implements the DSL used to
5
+ # configure controllers' use of the global session.
5
6
  module ActionControllerClassMethods
6
7
  def global_session_config
7
8
  unless @global_session_config
@@ -17,40 +18,32 @@ module GlobalSession
17
18
  end
18
19
 
19
20
  def has_global_session(options={})
20
- odefault = {:integrated=>false}
21
+ odefault = {:integrated=>false, :raise=>true}
21
22
  obase = self.superclass.global_session_options if self.superclass.respond_to?(:global_session_options)
23
+ obase ||= {}
22
24
  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
25
 
41
- before_filter :global_session_initialize, fopt
26
+ #ensure derived-class options don't conflict with mutually exclusive base-class options
27
+ options.delete(:only) if obase.has_key?(:only) && options.has_key?(:except)
28
+ options.delete(:except) if obase.has_key?(:except) && options.has_key?(:only)
29
+
30
+ #mark the global session as enabled (a hidden option) and store our
31
+ #calculated, merged options
32
+ options[:enabled] = true
33
+ self.global_session_options = options
34
+
35
+ before_filter :global_session_initialize
42
36
  end
43
37
 
44
38
  def no_global_session
39
+ @global_session_options[:enabled] = false if @global_session_options
45
40
  skip_before_filter :global_session_initialize
46
- before_filter :global_session_skip_renew
47
- before_filter :global_session_skip_update
48
41
  end
49
42
 
50
- protected
51
-
52
43
  def global_session_options
53
- @global_session_options || {}
44
+ obase = self.superclass.global_session_options if self.superclass.respond_to?(:global_session_options)
45
+ obase ||= {}
46
+ @global_session_options || obase
54
47
  end
55
48
 
56
49
  def global_session_options=(options)
@@ -15,7 +15,10 @@ module GlobalSession
15
15
  #
16
16
  module ActionControllerInstanceMethods
17
17
  def self.included(base) # :nodoc:
18
- base.alias_method_chain :session, :global_session
18
+ #Make sure a superclass hasn't already chained the methods...
19
+ unless base.instance_methods.include?("session_without_global_session")
20
+ base.alias_method_chain :session, :global_session
21
+ end
19
22
  end
20
23
 
21
24
  # Shortcut accessor for global session configuration object.
@@ -27,7 +30,7 @@ module GlobalSession
27
30
  end
28
31
 
29
32
  def global_session_options
30
- self.class.instance_variable_get(:@global_session_options)
33
+ self.class.global_session_options
31
34
  end
32
35
 
33
36
  # Global session reader.
@@ -44,7 +47,7 @@ module GlobalSession
44
47
  # === Return
45
48
  # session(IntegratedSession):: the integrated session
46
49
  def session_with_global_session
47
- if global_session
50
+ if global_session_options[:integrated] && global_session
48
51
  unless @integrated_session &&
49
52
  (@integrated_session.local == session_without_global_session) &&
50
53
  (@integrated_session.global == global_session)
@@ -63,14 +66,26 @@ module GlobalSession
63
66
  # === Return
64
67
  # true:: Always returns true
65
68
  def global_session_initialize
66
- options = self.class.instance_variable_get(:@global_session_options) || {}
69
+ options = global_session_options
70
+
71
+ if options[:only] && !options[:only].include?(action_name)
72
+ should_skip = true
73
+ elsif options[:except] && options[:except].include?(action_name)
74
+ should_skip = true
75
+ elsif !options[:enabled]
76
+ should_skip = true
77
+ end
67
78
 
68
- if (error = request.env['global_session.error'])
69
- raise error unless options[:raise] == false
79
+ if should_skip
80
+ request.env['global_session.req.renew'] = false
81
+ request.env['global_session.req.update'] = false
70
82
  else
83
+ error = request.env['global_session.error']
84
+ raise error unless error.nil? || options[:raise] == false
71
85
  @global_session = request.env['global_session']
72
- return true
73
86
  end
87
+
88
+ return true
74
89
  end
75
90
 
76
91
  # Filter to disable auto-renewal of the session.
@@ -1,21 +1,26 @@
1
1
  basedir = File.dirname(__FILE__)
2
2
 
3
3
  require 'rack/contrib/cookies'
4
-
4
+ require 'action_pack'
5
+ require 'action_controller'
5
6
 
6
7
  #Require the files necessary for Rails integration
7
8
  require 'global_session/rack'
8
9
  require 'global_session/rails/action_controller_class_methods'
9
10
  require 'global_session/rails/action_controller_instance_methods'
10
11
 
12
+ # Enable ActionController integration.
13
+ class <<ActionController::Base
14
+ include GlobalSession::Rails::ActionControllerClassMethods
15
+ end
16
+
17
+ ActionController::Base.instance_eval do
18
+ include GlobalSession::Rails::ActionControllerInstanceMethods
19
+ end
20
+
11
21
  module GlobalSession
12
22
  module Rails
13
23
  def self.activate(config)
14
- # Enable ActionController integration.
15
- class <<ActionController::Base
16
- include GlobalSession::Rails::ActionControllerClassMethods
17
- end
18
-
19
24
  authorities = File.join(::Rails.root, 'config', 'authorities')
20
25
  hgs_config = ActionController::Base.global_session_config
21
26
  hgs_dir = GlobalSession::Directory.new(hgs_config, authorities)
@@ -27,4 +32,4 @@ module GlobalSession
27
32
  return true
28
33
  end
29
34
  end
30
- end
35
+ end
@@ -111,6 +111,8 @@ module GlobalSession
111
111
  @signed.has_key?(key) || @insecure.has_key?(key)
112
112
  end
113
113
 
114
+ alias :key? :has_key?
115
+
114
116
  # Return the keys that are currently present in the global session.
115
117
  #
116
118
  # === Return
@@ -61,3 +61,9 @@ require File.join(basedir, 'global_session', 'directory')
61
61
  require File.join(basedir, 'global_session', 'encoding')
62
62
  require File.join(basedir, 'global_session', 'session')
63
63
  require File.join(basedir, 'global_session', 'integrated_session')
64
+
65
+ #Preemptively try to activate the Rails plugin, ignoring errors
66
+ begin
67
+ require File.join(basedir, 'global_session', 'rails')
68
+ rescue Exception => e
69
+ end
data/rails/init.rb CHANGED
@@ -1,3 +1,2 @@
1
- #
2
- # Empty placeholder file so Rails 2.x will recognize us as a plugin
3
- #
1
+ require 'global_session'
2
+ require 'global_session/rails'
@@ -0,0 +1,30 @@
1
+ class GlobalSessionGenerator < 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
+
18
+ puts "*** IMPORTANT - WORK IS REQUIRED ***"
19
+ puts "In order to make use of the global session, you will need to ensure that it"
20
+ puts "is installed to the Rack middleware stack. You can do so by adding an extra"
21
+ puts "line in your environment.rb inside the Rails initializer block, like so:"
22
+ puts
23
+ puts " Rails::Initializer.run do |config|"
24
+ puts "ADD>> require 'global_session'"
25
+ puts "ADD>> GlobalSession::Rails.activate(config)"
26
+ puts " end"
27
+
28
+ end
29
+ end
30
+ end
@@ -8,10 +8,8 @@ common:
8
8
  # Untrusted attributes of the global session
9
9
  insecure:
10
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
11
+ #If the session cookie is ephemeral, it goes away when the user closes the browser.
12
+ #Otherwise it stays around
15
13
  ephemeral: false
16
14
 
17
15
  # Test/spec runs
@@ -19,7 +17,7 @@ test:
19
17
  timeout: 15 #minutes
20
18
  renew: 5 #minutes before expiration
21
19
  cookie:
22
- name: _session_gbl
20
+ name: global_session
23
21
  #the name of the local authority (optional)
24
22
  authority: test
25
23
  #which authorities this app will trust
@@ -31,7 +29,7 @@ development:
31
29
  timeout: 60
32
30
  renew: 15
33
31
  cookie:
34
- name: _session_gbl
32
+ name: global_session
35
33
  authority: development
36
34
  trust:
37
35
  - development
@@ -42,7 +40,7 @@ production:
42
40
  timeout: 60
43
41
  renew: 15
44
42
  cookie:
45
- name: _session_gbl
43
+ name: global_session
46
44
  domain: <%= app_domain %>
47
45
  authority: production
48
46
  trust:
@@ -4,7 +4,7 @@ class GlobalSessionAuthorityGenerator < Rails::Generator::Base
4
4
 
5
5
  @app_name = File.basename(::Rails.root)
6
6
  @auth_name = args.shift
7
- raise ArgumentError, "Must specify name for global session authority, e.g. 'mycoolapp'" unless @auth_name
7
+ raise ArgumentError, "Must specify name for global session authority, e.g. 'prod'" unless @auth_name
8
8
  end
9
9
 
10
10
  def manifest
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: global_session
3
3
  version: !ruby/object:Gem::Version
4
- hash: 59
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
+ - 1
7
8
  - 0
8
- - 9
9
9
  - 0
10
- version: 0.9.0
10
+ version: 1.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-07 00:00:00 -08:00
18
+ date: 2011-01-01 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -169,11 +169,11 @@ files:
169
169
  - lib/global_session/rails/action_controller_instance_methods.rb
170
170
  - lib/global_session/session.rb
171
171
  - rails/init.rb
172
+ - rails_generators/global_session/USAGE
173
+ - rails_generators/global_session/global_session_generator.rb
174
+ - rails_generators/global_session/templates/global_session.yml.erb
172
175
  - rails_generators/global_session_authority/USAGE
173
176
  - rails_generators/global_session_authority/global_session_authority_generator.rb
174
- - rails_generators/global_session_config/USAGE
175
- - rails_generators/global_session_config/global_session_config_generator.rb
176
- - rails_generators/global_session_config/templates/global_session.yml.erb
177
177
  has_rdoc: true
178
178
  homepage: http://github.com/xeger/global_session
179
179
  licenses: []
@@ -1,19 +0,0 @@
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