has_global_session 0.8.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 ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Tony Spataro <code@tracker.xeger.net>
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.
data/README ADDED
@@ -0,0 +1,151 @@
1
+ = Introduction
2
+
3
+ HasGlobalSession enables multiple heterogeneous Web applications to share
4
+ session state in a cryptographically secure way, facilitating single sign-on
5
+ and enabling easier development of large-scale distributed applications that
6
+ make use of architectural strategies such as sharding or separation of concerns.
7
+
8
+ This plugin does not provide a complete solution for authentication. In
9
+ particular, it does *not* provide any of the following:
10
+
11
+ * <b>federation</b> -- aka cross-domain single sign-on -- use OpenID for that.
12
+
13
+ * <b>authentication</b> -- the application's controllers must authenticate the user.
14
+
15
+ * <b>secrecy</b> -- global session attributes can be signed but never encrypted;
16
+ protect against third-party snooping using SSL, and if you don't want your
17
+ users to see their session state, put it in a database.
18
+
19
+ * <b>replication</b> -- the authentication authorities must have some way to
20
+ share information about the database of users, their passwords, etc.
21
+
22
+ * <b>single sign-out</b> -- the authorities must have some way to broadcast a
23
+ notification when sessions are invalidated; they can override the default
24
+ Directory implementation to do realtime revocation checking.
25
+
26
+ == Global Session Contents
27
+
28
+ Global session state is stored as a cookie in the user's browser. The cookie
29
+ is a Zlib-compressed JSON dictionary containing the following stuff:
30
+ * session metadata (UUID, created-at, expires-at, certifying-authority)
31
+ * signed session attributes (e.g. the authenticated user ID)
32
+ * insecure session attributes (e.g. the last-visited URL)
33
+ * a cryptographic signature of the metadata and signed attributes
34
+
35
+ The global session is unserialized and its signature is verified whenever
36
+ a controller asks for the global session. The cookie's value is updated
37
+ whenever its attributes change. As an optimization, the signature is only
38
+ recomputed when the metadata or signed attributes have changed; insecure
39
+ attributes can change "for free."
40
+
41
+ Because the security properties of attributes can vary, HasGlobalSession
42
+ requires all _possible_ attributes to be declared up-front in the config
43
+ file. The 'attributes' section of the config file defines the _schema_
44
+ for the global session: which attributes can be used, and which must be
45
+ cryptographically signed in order to be used.
46
+
47
+ Since the session is serialized as JSON, only a limited range of object
48
+ types can be stored in it: strings, numbers, lists, hashes and other Ruby
49
+ primitives. Ruby booleans (true/false) do not translate well into JSON
50
+ and should be avoided.
51
+
52
+ = Example
53
+
54
+ 1) Create a basic config file and edit it to suit your needs:
55
+ $ script/generate global_session_config mycoolapp.com
56
+
57
+ 2) Create an authentication authority:
58
+ $ script/generate global_session_authority mycoolapp
59
+
60
+ 3) Declare that some or all of your application's controllers will have access
61
+ to the global session:
62
+ class ApplicationController < ActionController::Base
63
+ has_global_session
64
+ end
65
+
66
+ 4) Make use of the global session hash in your controllers:
67
+ global_session['user'] = @user.id
68
+ ...
69
+ @current_user = User.find(global_session['user'])
70
+
71
+ = Detailed Information
72
+
73
+ == Global Session Domain
74
+
75
+ We refer to collection of _all_ Web application instances capable of using the
76
+ global session as the "domain." The global session domain may consist of any
77
+ number of distinct servers, possibly hidden behind load balancers or proxies.
78
+ The servers within the domain may all be running the same Rails application,
79
+ or they may be running different codebases that represent different parts of
80
+ a distributed application. (They may also be using app frameworks other than
81
+ Rails.)
82
+
83
+ The only constraint imposed by HasGlobalSession is that all servers within the
84
+ domain must have end-user-facing URLs within the same second-level DNS domain.
85
+ This is due to limitations imposed by the HTTP cookie mechanism: for privacy
86
+ reasons, cookies will only be sent to servers within the same domain as the
87
+ server that first created them.
88
+
89
+ For example, in my HasGlobalSession configuration file I might specify that my
90
+ cookie's domain is "example.com". My app servers at app1.example.com and
91
+ app2.example.com would be part of the global session domain, but my business
92
+ partner's application at www.partner.com could not participate.
93
+
94
+ == Authorities and Relying Parties
95
+
96
+ A Web application that can create or update the global session is said to
97
+ be an "authority" (because it's trusted by other parties to make assertions
98
+ about global session state). An application that can read the global session
99
+ is said to be a "relying party." In practice, every application is a relying
100
+ party but not all of them need to be authorities.
101
+
102
+ There is an RSA key pair associated with each authority. The authority's
103
+ public key is distribued to all relying parties, but the private key must
104
+ remain a secret to that authority (which may consist of many individual
105
+ servers).
106
+
107
+ This system allows for significant flexibility when configuring a distributed
108
+ app's global session. There must be at least one authority, but for many apps
109
+ one authority (plus an arbitrary number of relying parties, which do not need
110
+ a key pair) will be sufficient.
111
+
112
+ In general, two systems should be part of the same authority if there is no
113
+ trust boundary between them -- that is to say, trust between the two systems
114
+ is unlimited in both directions.
115
+
116
+ Here are some reasons you might consider dividing your systems into different
117
+ authorities:
118
+ * beta/staging system vs. production system
119
+ * system hosted by a third party vs. system hosted internally
120
+ * e-commerce server vs. storefront server
121
+
122
+ == The Directory
123
+
124
+ The Directory is a Ruby object instantiated by HasGlobalSession in order to
125
+ perform lookups of public and private keys. Given an authority name (as found
126
+ in a session cookie), the Directory can find the corresponding public key.
127
+
128
+ If the local system is an authority itself, the method #my_authority_name will
129
+ return non-nil and #my_private_key will return a private key suitable for
130
+ signing session attributes.
131
+
132
+ The Directory implementation included with HasGlobalSession uses the filesystem
133
+ as the backing store for its key pairs. Its #initialize method accepts a
134
+ filesystem path that will be searched for *.pub and *.key files containing PEM-
135
+ encoded public and private keys (the same format used by OpenSSH).
136
+
137
+ When used with a Rails app, HasGlobalSession expects to find its keystore in
138
+ config/authorities. You can use the global_session generator to create new key
139
+ pairs. Remember never to check a *.key file into a public repository!! (In
140
+ contrast, *.pub files can be distributed freely.)
141
+
142
+ If you wish all of the systems to stop trusting an authority, simply delete
143
+ its public key from config/authorities.
144
+
145
+ = To-Do
146
+
147
+ * Configurable session expiry
148
+ * Option to auto-renew session
149
+ * Option for non-sticky global session (cookie expires at close of browser session, not at global session expiration!)
150
+
151
+ Copyright (c) 2010 Tony Spataro <code@tracker.xeger.net>, released under the MIT license
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.required_rubygems_version = nil if s.respond_to? :required_rubygems_version=
7
+ s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
+
9
+ s.name = 'has_global_session'
10
+ s.version = '0.8.0'
11
+ s.date = '2010-06-01'
12
+
13
+ s.authors = ['Tony Spataro']
14
+ s.email = 'code@tracker.xeger.net'
15
+ s.homepage= 'http://github.com/xeger/has_global_session'
16
+
17
+ s.summary = %q{Secure single-domain session sharing plugin for Rails.}
18
+ s.description = %q{This Rails plugin allows several Rails web apps that share the same back-end user database to share session state in a cryptographically secure way, facilitating single sign-on in a distributed web app. It only provides session sharing and does not concern itself with authentication or replication of the user database.}
19
+
20
+ s.add_runtime_dependency('uuidtools', [">= 2.1.1"])
21
+
22
+ basedir = File.dirname(__FILE__)
23
+ candidates = ['has_global_session.gemspec', 'init.rb', 'MIT-LICENSE', 'README'] +
24
+ Dir['lib/**/*'] +
25
+ Dir['rails/**/*']
26
+ s.files = candidates.sort
27
+ end
28
+
29
+ if $PROGRAM_NAME == __FILE__
30
+ Gem.manage_gems if Gem::RubyGemsVersion.to_f < 1.0
31
+ Gem::Builder.new(spec).build
32
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ # Stub to invoke real init.rb when HasGlobalSession is installed as a Rails
2
+ # plugin.
3
+ basedir = File.dirname(__FILE__)
4
+ require File.join(basedir, 'rails', 'init')
@@ -0,0 +1,2 @@
1
+ ./script/generate global_session config <DNS domain for cookie>
2
+ ./script/generate global_session authority <name of authority>
@@ -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 'templates/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,30 @@
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
+ # but it seldom makes sense to do so.
4
+ common:
5
+ attributes:
6
+ # Signed attributes of the global session
7
+ signed:
8
+ - user
9
+ # Untrusted attributes of the global session
10
+ insecure:
11
+ - account
12
+ # Enable local session integration in order to use the ActionController
13
+ # method #session to access both local AND global session state, with
14
+ # global attributes always taking precedence over local attributes.
15
+ integrated: true
16
+
17
+ development:
18
+ cookie:
19
+ name: __<%= app_name %>_development
20
+ domain: localhost
21
+
22
+ test:
23
+ cookie:
24
+ name: __<%= app_name %>_test
25
+ domain: localhost
26
+
27
+ production:
28
+ cookie:
29
+ name: __<%= app_name %>_global
30
+ domain: <%= app_domain %>
@@ -0,0 +1,34 @@
1
+ module HasGlobalSession
2
+ module Configuration
3
+ mattr_accessor :config_file
4
+ mattr_accessor :environment
5
+
6
+ def self.[](key)
7
+ unless @config
8
+ raise MissingConfiguration, "config_file is nil; cannot read configuration" unless config_file
9
+ raise MissingConfiguration, "environment is nil; must be specified" unless environment
10
+ @config = YAML.load(File.read(config_file))
11
+ validate
12
+ end
13
+ if @config.has_key?(environment) && @config[environment].has_key?(key)
14
+ return @config[environment][key]
15
+ else
16
+ @config['common'][key]
17
+ end
18
+ end
19
+
20
+ def self.validate
21
+ ['attributes/signed', 'integrated', 'cookie/name', 'cookie/domain'].each do |path|
22
+ elements = path.split '/'
23
+ object = self[elements.shift]
24
+ elements.each do |element|
25
+ object = object[element]
26
+ if object.nil?
27
+ msg = "#{File.basename(config_file)} does not specify required element #{elements.map { |x| "['#{x}']"}.join('')}"
28
+ raise MissingConfiguration, msg
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ module HasGlobalSession
2
+ class Directory
3
+ attr_reader :authorities, :my_private_key, :my_authority_name
4
+
5
+ def initialize(keystore_directory)
6
+ certs = Dir[File.join(keystore_directory, '*.pub')]
7
+ keys = Dir[File.join(keystore_directory, '*.key')]
8
+
9
+ @authorities = {}
10
+ certs.each do |cert_file|
11
+ basename = File.basename(cert_file)
12
+ authority = basename[0...(basename.rindex('.'))] #chop trailing .ext
13
+ @authorities[authority] = OpenSSL::PKey::RSA.new(File.read(cert_file))
14
+ raise TypeError, "Expected #{basename} to contain an RSA public key" unless @authorities[authority].public?
15
+ end
16
+
17
+ raise ArgumentError, "Excepted 0 or 1 key files, found #{keys.size}" if ![0, 1].include?(keys.size)
18
+ if (key_file = keys[0])
19
+ basename = File.basename(key_file)
20
+ @my_private_key = OpenSSL::PKey::RSA.new(File.read(key_file))
21
+ raise TypeError, "Expected #{basename} to contain an RSA private key" unless @my_private_key.private?
22
+ @my_authority_name = basename[0...(basename.rindex('.'))] #chop trailing .ext
23
+ end
24
+ end
25
+
26
+ def invalidated_session?(uuid)
27
+ false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,152 @@
1
+ # Standard library dependencies
2
+ require 'set'
3
+ require 'zlib'
4
+
5
+ # Gem dependencies
6
+ require 'uuidtools'
7
+
8
+ module HasGlobalSession
9
+ class GlobalSession
10
+ attr_reader :id, :authority, :created_at, :expires_at
11
+
12
+ def initialize(directory, cookie=nil)
13
+ @schema_signed = Set.new((Configuration['attributes']['signed'] rescue []))
14
+ @schema_insecure = Set.new((Configuration['attributes']['insecure'] rescue []))
15
+ @directory = directory
16
+
17
+ if cookie
18
+ #User presented us with a cookie; let's decrypt and verify it
19
+ zbin = Base64.decode64(cookie)
20
+ json = Zlib::Inflate.inflate(zbin)
21
+ hash = ActiveSupport::JSON.decode(json)
22
+ @id = hash['id']
23
+ @created_at = Time.at(hash['tc'].to_i)
24
+ @expires_at = Time.at(hash['te'].to_i)
25
+ @signed = hash['ds']
26
+ @insecure = hash['dx']
27
+ @signature = hash['s']
28
+ @authority = hash['a']
29
+
30
+ hash.delete('s')
31
+ expected = digest(hash)
32
+ signer = @directory.authorities[@authority]
33
+ raise SecurityError, "Unknown signing authority #{@authority}" unless signer
34
+ got = signer.public_decrypt(Base64.decode64(@signature))
35
+ unless (got == expected)
36
+ raise SecurityError, "Signature mismatch on global session cookie; tampering suspected"
37
+ end
38
+
39
+ if expired? || @directory.invalidated_session?(@id)
40
+ raise ExpiredSession, "Global session cookie has expired"
41
+ end
42
+
43
+ else
44
+ @signed = {}
45
+ @insecure = {}
46
+ @id = UUIDTools::UUID.timestamp_create.to_s
47
+ @created_at = Time.now.utc
48
+ @expires_at = 2.hours.from_now.utc #TODO configurable
49
+ @authority = @directory.my_authority_name
50
+ @dirty_secure = true
51
+ end
52
+ end
53
+
54
+ def supports_key?(key)
55
+ @schema_signed.include?(key) || @schema_insecure.include?(key)
56
+ end
57
+
58
+ def expired?
59
+ (@expires_at <= Time.now)
60
+ end
61
+
62
+ def expire!
63
+ @expires_at = Time.at(0)
64
+ @dirty_secure = true
65
+ end
66
+
67
+ def to_s
68
+ hash = {'id'=>@id,
69
+ 'tc'=>@created_at.to_i, 'te'=>@expires_at.to_i,
70
+ 'ds'=>@signed, 'dx'=>@insecure}
71
+
72
+ if @signature && !@dirty_secure
73
+ #use cached signature unless we've changed secure state
74
+ authority = @authority
75
+ signature = @signature
76
+ else
77
+ authority = @directory.my_authority_name
78
+ hash['a'] = authority
79
+ digest = digest(hash)
80
+ signature = Base64.encode64(@directory.my_private_key.private_encrypt(digest))
81
+ end
82
+
83
+ hash['s'] = signature
84
+ hash['a'] = authority
85
+ json = ActiveSupport::JSON.encode(hash)
86
+ zbin = Zlib::Deflate.deflate(json, Zlib::BEST_COMPRESSION)
87
+ return Base64.encode64(zbin)
88
+ end
89
+
90
+ def [](key)
91
+ @signed[key] || @insecure[key]
92
+ end
93
+
94
+ def []=(key, value)
95
+ if @schema_signed.include?(key)
96
+ unless @directory.my_private_key && @directory.my_authority_name
97
+ raise StandardError, 'Cannot change secure session attributes; we are not an authority'
98
+ end
99
+
100
+ @signed[key] = value
101
+ @dirty_secure = true
102
+ elsif @schema_insecure.include?(key)
103
+ @insecure[key] = value
104
+ else
105
+ raise ArgumentError, "Attribute '#{key}' is not specified in global session configuration"
106
+ end
107
+ end
108
+
109
+ def has_key?(key)
110
+ @signed.has_key(key) || @insecure.has_key?(key)
111
+ end
112
+
113
+ def keys
114
+ @signed.keys + @insecure.keys
115
+ end
116
+
117
+ def values
118
+ @signed.values + @insecure.values
119
+ end
120
+
121
+ def each_pair(&block)
122
+ @signed.each_pair(&block)
123
+ @insecure.each_pair(&block)
124
+ end
125
+
126
+ private
127
+
128
+ def digest(input)
129
+ canonical = ActiveSupport::JSON.encode(canonicalize(input))
130
+ return Digest::SHA1.new().update(canonical).hexdigest
131
+ end
132
+
133
+ def canonicalize(input)
134
+ case input
135
+ when Hash
136
+ output = ActiveSupport::OrderedHash.new
137
+ ordered_keys = input.keys.sort
138
+ ordered_keys.each do |key|
139
+ output[canonicalize(key)] = canonicalize(input[key])
140
+ end
141
+ when Array
142
+ output = input.collect { |x| canonicalize(x) }
143
+ when Numeric, String, ActiveSupport::OrderedHash
144
+ output = input
145
+ else
146
+ raise TypeError, "Objects of type #{input.class.name} cannot be serialized in the global session"
147
+ end
148
+
149
+ return output
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,41 @@
1
+ module HasGlobalSession
2
+ class IntegratedSession
3
+ def initialize(local_session, global_session)
4
+ @local_session = local_session
5
+ @global_session = global_session
6
+ end
7
+
8
+ def [](key)
9
+ if @global_session.supports_key?(key)
10
+ @global_session[key]
11
+ else
12
+ @local_session[key]
13
+ end
14
+ end
15
+
16
+ def []=(key, value)
17
+ if @global_session.supports_key?(key)
18
+ @global_session[key] = value
19
+ else
20
+ @local_session[key] = value
21
+ end
22
+ end
23
+
24
+ def has_key?(key)
25
+ @global_session.has_key(key) || @local_session.has_key?(key)
26
+ end
27
+
28
+ def keys
29
+ @global_session.keys + @local_session.keys
30
+ end
31
+
32
+ def values
33
+ @global_session.values + @local_session.values
34
+ end
35
+
36
+ def each_pair(&block)
37
+ @global_session.each_pair(&block)
38
+ @local_session.each_pair(&block)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ module HasGlobalSession
2
+ class MissingConfiguration < Exception; end
3
+ class SessionExpired < Exception; end
4
+ end
5
+
6
+ basedir = File.dirname(__FILE__)
7
+ require File.join(basedir, 'has_global_session', 'configuration')
8
+ require File.join(basedir, 'has_global_session', 'directory')
9
+ require File.join(basedir, 'has_global_session', 'global_session')
10
+ require File.join(basedir, 'has_global_session', 'integrated_session')
@@ -0,0 +1,48 @@
1
+ module HasGlobalSession
2
+ module ActionControllerInstanceMethods
3
+ def global_session
4
+ return @global_session if @global_session
5
+
6
+ begin
7
+ cookie = cookies[Configuration['cookie']['name']]
8
+ directory = Directory.new(File.join(RAILS_ROOT, 'config', 'authorities'))
9
+
10
+ begin
11
+ #unserialize the global session from the cookie, or
12
+ #initialize a new global session if cookie == nil
13
+ @global_session = GlobalSession.new(directory, cookie)
14
+ rescue SessionExpired
15
+ #if the cookie is present but expired, silently
16
+ #initialize a new global session
17
+ @global_session = GlobalSession.new(directory)
18
+ end
19
+ rescue Exception => e
20
+ cookies.delete Configuration['cookie']['name']
21
+ raise e
22
+ end
23
+ end
24
+
25
+ if Configuration['integrated']
26
+ def session
27
+ @integrated_session ||= IntegratedSession.new(super, global_session)
28
+ return @integrated_session
29
+ end
30
+ end
31
+
32
+ def global_session_update_cookie
33
+ if @global_session
34
+ if @global_session.expired?
35
+ options = {:value => nil,
36
+ :domain => Configuration['cookie']['domain'],
37
+ :expires => Time.at(0)}
38
+ else
39
+ options = {:value => @global_session.to_s,
40
+ :domain => Configuration['cookie']['domain'],
41
+ :expires => @global_session.expires_at}
42
+ end
43
+
44
+ cookies[Configuration['cookie']['name']] = options
45
+ end
46
+ end
47
+ end
48
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,18 @@
1
+ basedir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
2
+ require File.join(basedir, 'lib', 'has_global_session')
3
+
4
+ # Tie the Configuration module to Rails' filesystem structure
5
+ # and operating environment.
6
+ HasGlobalSession::Configuration.config_file =
7
+ File.join(RAILS_ROOT, 'config', 'global_session.yml')
8
+ HasGlobalSession::Configuration.environment = RAILS_ENV
9
+
10
+ require File.join(basedir, 'rails', 'action_controller_instance_methods')
11
+
12
+ # Enable ActionController integration.
13
+ class ActionController::Base
14
+ def self.has_global_session
15
+ include HasGlobalSession::ActionControllerInstanceMethods
16
+ after_filter :global_session_update_cookie
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_global_session
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Tony Spataro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-06-01 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: uuidtools
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.1
24
+ version:
25
+ description: This Rails plugin allows several Rails web apps that share the same back-end user database to share session state in a cryptographically secure way, facilitating single sign-on in a distributed web app. It only provides session sharing and does not concern itself with authentication or replication of the user database.
26
+ email: code@tracker.xeger.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - MIT-LICENSE
35
+ - README
36
+ - has_global_session.gemspec
37
+ - init.rb
38
+ - lib/generators/global_session_config/USAGE
39
+ - lib/generators/global_session_config/global_session_config_generator.rb
40
+ - lib/generators/global_session_config/templates/global_session.yml.erb
41
+ - lib/has_global_session.rb
42
+ - lib/has_global_session/configuration.rb
43
+ - lib/has_global_session/directory.rb
44
+ - lib/has_global_session/global_session.rb
45
+ - lib/has_global_session/integrated_session.rb
46
+ - rails/action_controller_instance_methods.rb
47
+ - rails/init.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/xeger/has_global_session
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.8.7
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Secure single-domain session sharing plugin for Rails.
76
+ test_files: []
77
+