has_global_session 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
+