devise_cas_authenticatable 1.0.0.alpha11 → 1.0.0.alpha12

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/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .bundle/*
2
+ .idea/*
2
3
  .yardoc/*
3
4
  pkg/*
4
5
  spec/scenario/db/*.sqlite3
data/Gemfile.lock CHANGED
@@ -17,7 +17,7 @@ GIT
17
17
  PATH
18
18
  remote: .
19
19
  specs:
20
- devise_cas_authenticatable (1.0.0.alpha11)
20
+ devise_cas_authenticatable (1.0.0.alpha12)
21
21
  devise (>= 1.0.6)
22
22
  rubycas-client (>= 2.2.1)
23
23
 
@@ -69,7 +69,7 @@ GEM
69
69
  configuration (1.2.0)
70
70
  crypt-isaac (0.9.1)
71
71
  culerity (0.2.15)
72
- devise (1.4.5)
72
+ devise (1.4.7)
73
73
  bcrypt-ruby (~> 3.0)
74
74
  orm_adapter (~> 0.0.3)
75
75
  warden (~> 1.0.3)
data/README.md CHANGED
@@ -110,5 +110,4 @@ See also
110
110
  TODO
111
111
  ----
112
112
 
113
- * Implement CAS single sign-off support (maybe via a Rack middleware?)
114
113
  * Test on non-ActiveRecord ORMs
@@ -16,6 +16,11 @@ class Devise::CasSessionsController < Devise::SessionsController
16
16
  end
17
17
 
18
18
  def destroy
19
+ # Delete the ticket->session ID mapping if one exists for this session
20
+ if ticket = session['cas_last_valid_ticket']
21
+ ::DeviseCasAuthenticatable::SingleSignOut::Strategies.current_strategy.delete_session_index(ticket)
22
+ end
23
+
19
24
  # if :cas_create_user is false a CAS session might be open but not signed_in
20
25
  # in such case we destroy the session here
21
26
  if signed_in?(resource_name)
@@ -29,8 +34,53 @@ class Devise::CasSessionsController < Devise::SessionsController
29
34
  destination << after_sign_out_path_for(resource_name)
30
35
  redirect_to(::Devise.cas_client.logout_url(destination))
31
36
  end
32
-
33
- private
37
+
38
+ def single_sign_out
39
+ if ::Devise.cas_enable_single_sign_out
40
+ session_index = read_session_index
41
+ if session_index
42
+ logger.debug "Intercepted single-sign-out request for CAS session #{session_index}."
43
+ session_id = ::DeviseCasAuthenticatable::SingleSignOut::Strategies.current_strategy.find_session_id_by_index(session_index)
44
+ if session_id
45
+ destroy_cas_session(session_id, session_index)
46
+ end
47
+ else
48
+ logger.warn "Ignoring CAS single-sign-out request as no session index could be parsed from the parameters."
49
+ end
50
+ else
51
+ logger.warn "Ignoring CAS single-sign-out request as feature is not currently enabled."
52
+ end
53
+
54
+ render :nothing => true
55
+ end
56
+
57
+ private
58
+
59
+ def read_session_index
60
+ if request.headers['CONTENT_TYPE'] =~ %r{^multipart/}
61
+ false
62
+ elsif request.post? && params['logoutRequest'] =~
63
+ %r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m
64
+ $~[1]
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ def destroy_cas_session(session_id, session_index)
71
+ if env[:session_store] && env[:session_store].respond_to?(:destroy_session)
72
+ if env[:session_store].destroy_session(session_id)
73
+ logger.debug "Destroyed session #{session_id} corresponding to service ticket #{session_index}."
74
+ else
75
+ logger.debug "Data for session #{session_id} was not found. It may have already been cleared by a local CAS logout request."
76
+ end
77
+ else
78
+ logger.warn "A single sign out request was received for ticket #{session_index} but the Rails session_store is not a type supported for single-sign-out by devise_cas_authenticatable."
79
+ end
80
+
81
+ ::DeviseCasAuthenticatable::SingleSignOut::Strategies.current_strategy.delete_session_index(session_index)
82
+ end
83
+
34
84
  def returning_from_cas?
35
85
  params[:ticket] || request.referer =~ /^#{::Devise.cas_client.cas_base_url}/
36
86
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{devise_cas_authenticatable}
5
- s.version = "1.0.0.alpha11"
5
+ s.version = "1.0.0.alpha12"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Nat Budin"]
8
+ s.authors = ["Nat Budin", "Jeremy Haile"]
9
9
  s.description = %q{CAS authentication module for Devise}
10
10
  s.email = %q{natbudin@gmail.com}
11
11
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -7,7 +7,8 @@ if ActionController::Routing.name =~ /ActionDispatch/
7
7
  def devise_cas_authenticatable(mapping, controllers)
8
8
  # service endpoint for CAS server
9
9
  get "service", :to => "#{controllers[:cas_sessions]}#service", :as => "service"
10
-
10
+ post "service", :to => "#{controllers[:cas_sessions]}#single_sign_out", :as => "single_sign_out"
11
+
11
12
  resource :session, :only => [], :controller => controllers[:cas_sessions], :path => "" do
12
13
  get :new, :path => mapping.path_names[:sign_in], :as => "new"
13
14
  get :unregistered
@@ -25,6 +26,7 @@ else
25
26
  def cas_authenticatable(routes, mapping)
26
27
  routes.with_options(:controller => 'devise/cas_sessions', :name_prefix => nil) do |session|
27
28
  session.send(:"#{mapping.name}_service", '/service', :action => 'service', :conditions => {:method => :get})
29
+ session.send(:"#{mapping.name}_service", '/service', :action => 'single_sign_out', :conditions => {:method => :post})
28
30
  session.send(:"unregistered_#{mapping.name}_session", '/unregistered', :action => "unregistered", :conditions => {:method => :get})
29
31
  session.send(:"new_#{mapping.name}_session", mapping.path_names[:sign_in], :action => 'new', :conditions => {:method => :get})
30
32
  session.send(:"#{mapping.name}_session", mapping.path_names[:sign_in], :action => 'create', :conditions => {:method => :post})
@@ -0,0 +1,12 @@
1
+ ActiveRecord::SessionStore.class_eval do
2
+
3
+ include DeviseCasAuthenticatable::SingleSignOut::SetSession
4
+ alias_method_chain :set_session, :storage
5
+
6
+ def destroy_session(sid)
7
+ if session = Session::find_by_session_id(sid)
8
+ session.destroy
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,27 @@
1
+ require "action_controller/session/redis_session_store"
2
+
3
+ module DeviseCasAuthenticatable
4
+ module SingleSignOut
5
+ module RedisSessionStore
6
+
7
+ include DeviseCasAuthenticatable::SingleSignOut::SetSession
8
+
9
+ def destroy_session(sid)
10
+ @pool.del(sid)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ if ::Redis::Store.rails3?
18
+ ActionDispatch::Session::RedisSessionStore.class_eval do
19
+ include DeviseCasAuthenticatable::SingleSignOut::RedisSessionStore
20
+ alias_method_chain :set_session, :storage
21
+ end
22
+ else
23
+ ActionController::Session::RedisSessionStore.class_eval do
24
+ include DeviseCasAuthenticatable::SingleSignOut::RedisSessionStore
25
+ alias_method_chain :set_session, :storage
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module DeviseCasAuthenticatable
2
+ module SingleSignOut
3
+ module Strategies
4
+ class Base
5
+ def logger
6
+ @logger ||= Rails.logger
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module DeviseCasAuthenticatable
2
+ module SingleSignOut
3
+ module Strategies
4
+ class RailsCache < Base
5
+ def store_session_id_for_index(session_index, session_id)
6
+ logger.debug("Storing #{session_id} for index #{session_index}")
7
+ Rails.cache.write(cache_key(session_index), session_id)
8
+ end
9
+
10
+ def find_session_id_by_index(session_index)
11
+ sid = Rails.cache.read(cache_key(session_index))
12
+ logger.debug("Found session id #{sid} for index #{session_index}")
13
+ sid
14
+ end
15
+
16
+ def delete_session_index(session_index)
17
+ logger.debug("Deleting index #{session_index}")
18
+ Rails.cache.delete(cache_key(session_index))
19
+ end
20
+
21
+ private
22
+
23
+ def cache_key(session_index)
24
+ "devise_cas_authenticatable:#{session_index}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ ::DeviseCasAuthenticatable::SingleSignOut::Strategies.add( :rails_cache, DeviseCasAuthenticatable::SingleSignOut::Strategies::RailsCache )
@@ -0,0 +1,58 @@
1
+ module DeviseCasAuthenticatable
2
+ module SingleSignOut
3
+ module Strategies
4
+ class << self
5
+
6
+ # Add a strategy and store it in a hash.
7
+ def add(label, strategy, &block)
8
+ strategy ||= Class.new(DeviseCasAuthenticatable::SingleSignOut::Strategies::Base)
9
+ strategy.class_eval(&block) if block_given?
10
+
11
+ check_method(label, strategy, :store_session_id_for_index)
12
+ check_method(label, strategy, :find_session_id_by_index)
13
+ check_method(label, strategy, :delete_session_index)
14
+
15
+ unless strategy.ancestors.include?(DeviseCasAuthenticatable::SingleSignOut::Strategies::Base)
16
+ raise "#{label.inspect} is not a #{base}"
17
+ end
18
+
19
+ _strategies[label] = strategy.new()
20
+ end
21
+
22
+ # Update a previously given strategy.
23
+ def update(label, &block)
24
+ strategy = _strategies[label]
25
+ raise "Unknown strategy #{label.inspect}" unless strategy
26
+ add(label, strategy, &block)
27
+ end
28
+
29
+ # Provides access to strategies by label
30
+ def [](label)
31
+ _strategies[label]
32
+ end
33
+
34
+ def current_strategy
35
+ self[::Devise.cas_single_sign_out_mapping_strategy]
36
+ end
37
+
38
+ # Clears all declared.
39
+ def clear!
40
+ _strategies.clear
41
+ end
42
+
43
+ private
44
+
45
+ def _strategies
46
+ @strategies ||= {}
47
+ end
48
+
49
+ def check_method(label, strategy, method)
50
+ unless strategy.method_defined?(method)
51
+ raise NoMethodError, "#{method.to_s} is not declared in the #{label.inspect} strategy"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ module DeviseCasAuthenticatable
2
+ module SingleSignOut
3
+ module SetSession
4
+ def set_session_with_storage(env, sid, session_data)
5
+ if session_data['cas_last_valid_ticket_store']
6
+ ::DeviseCasAuthenticatable::SingleSignOut::Strategies.current_strategy.store_session_id_for_index(session_data['cas_last_valid_ticket'], sid)
7
+ session_data['cas_last_valid_ticket_store'] = nil
8
+ end
9
+
10
+ set_session_without_storage(env, sid, session_data)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ if defined?(ActionDispatch)
17
+ # Need to make a small extension to rails to expose the session_store to our controllers
18
+ require 'action_dispatch/middleware/session/abstract_store'
19
+ ActionDispatch::Session::AbstractStore.class_eval do
20
+ def prepare_with_session_store!(env)
21
+ prepare_without_session_store!(env)
22
+ env[:session_store] = self
23
+ end
24
+ alias_method_chain :prepare!, :session_store
25
+ end
26
+ end
27
+
28
+ require 'devise_cas_authenticatable/single_sign_out/strategies'
29
+ require 'devise_cas_authenticatable/single_sign_out/strategies/base'
30
+ require 'devise_cas_authenticatable/single_sign_out/strategies/rails_cache'
@@ -17,6 +17,12 @@ module Devise
17
17
  ticket = read_ticket(params)
18
18
  if ticket
19
19
  if resource = mapping.to.authenticate_with_cas_ticket(ticket)
20
+ # Store the ticket in the session for later usage
21
+ if ::Devise.cas_enable_single_sign_out
22
+ session['cas_last_valid_ticket'] = ticket.ticket
23
+ session['cas_last_valid_ticket_store'] = true
24
+ end
25
+
20
26
  success!(resource)
21
27
  elsif ticket.is_valid?
22
28
  redirect!(::Devise.cas_unregistered_url(request.url, mapping), :username => ticket.response.user)
@@ -5,6 +5,16 @@ require 'devise_cas_authenticatable/routes'
5
5
  require 'devise_cas_authenticatable/strategy'
6
6
  require 'devise_cas_authenticatable/exceptions'
7
7
 
8
+ require 'devise_cas_authenticatable/single_sign_out'
9
+
10
+ if defined?(ActiveRecord::SessionStore)
11
+ require 'devise_cas_authenticatable/single_sign_out/session_store/active_record'
12
+ end
13
+
14
+ if defined?(Redis::Store)
15
+ require 'devise_cas_authenticatable/single_sign_out/session_store/redis'
16
+ end
17
+
8
18
  require 'rubycas-client'
9
19
 
10
20
  # Register as a Rails engine if Rails::Engine exists
@@ -31,7 +41,16 @@ module Devise
31
41
 
32
42
  # The login URL of the CAS server. If undefined, will default based on cas_base_url.
33
43
  @@cas_validate_url = nil
34
-
44
+
45
+ # Should devise_cas_authenticatable enable single-sign-out? Requires use of a supported
46
+ # session_store. Currently supports active_record or redis.
47
+ # False by default.
48
+ @@cas_enable_single_sign_out = false
49
+
50
+ # What strategy should single sign out use for tracking token->session ID mapping.
51
+ # :rails_cache by default.
52
+ @@cas_single_sign_out_mapping_strategy = :rails_cache
53
+
35
54
  # Should devise_cas_authenticatable attempt to create new user records for
36
55
  # unknown usernames? True by default.
37
56
  @@cas_create_user = true
@@ -43,12 +62,12 @@ module Devise
43
62
  # Name of the parameter passed in the logout query
44
63
  @@cas_destination_logout_param_name = nil
45
64
 
46
- mattr_accessor :cas_base_url, :cas_login_url, :cas_logout_url, :cas_validate_url, :cas_create_user, :cas_destination_logout_param_name, :cas_username_column
65
+ mattr_accessor :cas_base_url, :cas_login_url, :cas_logout_url, :cas_validate_url, :cas_create_user, :cas_destination_logout_param_name, :cas_username_column, :cas_enable_single_sign_out, :cas_single_sign_out_mapping_strategy
47
66
 
48
67
  def self.cas_create_user?
49
68
  cas_create_user
50
69
  end
51
-
70
+
52
71
  # Return a CASClient::Client instance based on configuration parameters.
53
72
  def self.cas_client
54
73
  @@cas_client ||= CASClient::Client.new(
@@ -56,7 +75,8 @@ module Devise
56
75
  :cas_base_url => @@cas_base_url,
57
76
  :login_url => @@cas_login_url,
58
77
  :logout_url => @@cas_logout_url,
59
- :validate_url => @@cas_validate_url
78
+ :validate_url => @@cas_validate_url,
79
+ :enable_single_sign_out => @@cas_enable_single_sign_out
60
80
  )
61
81
  end
62
82
 
metadata CHANGED
@@ -1,20 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_cas_authenticatable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha11
4
+ version: 1.0.0.alpha12
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nat Budin
9
+ - Jeremy Haile
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2011-09-16 00:00:00.000000000 -04:00
13
+ date: 2011-09-28 00:00:00.000000000 -04:00
13
14
  default_executable:
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: devise
17
- requirement: &2158960580 !ruby/object:Gem::Requirement
18
+ requirement: &2157571980 !ruby/object:Gem::Requirement
18
19
  none: false
19
20
  requirements:
20
21
  - - ! '>='
@@ -22,10 +23,10 @@ dependencies:
22
23
  version: 1.0.6
23
24
  type: :runtime
24
25
  prerelease: false
25
- version_requirements: *2158960580
26
+ version_requirements: *2157571980
26
27
  - !ruby/object:Gem::Dependency
27
28
  name: rubycas-client
28
- requirement: &2158959980 !ruby/object:Gem::Requirement
29
+ requirement: &2157571440 !ruby/object:Gem::Requirement
29
30
  none: false
30
31
  requirements:
31
32
  - - ! '>='
@@ -33,10 +34,10 @@ dependencies:
33
34
  version: 2.2.1
34
35
  type: :runtime
35
36
  prerelease: false
36
- version_requirements: *2158959980
37
+ version_requirements: *2157571440
37
38
  - !ruby/object:Gem::Dependency
38
39
  name: rails
39
- requirement: &2158959480 !ruby/object:Gem::Requirement
40
+ requirement: &2157570280 !ruby/object:Gem::Requirement
40
41
  none: false
41
42
  requirements:
42
43
  - - ! '>='
@@ -44,10 +45,10 @@ dependencies:
44
45
  version: 3.0.7
45
46
  type: :development
46
47
  prerelease: false
47
- version_requirements: *2158959480
48
+ version_requirements: *2157570280
48
49
  - !ruby/object:Gem::Dependency
49
50
  name: rspec-rails
50
- requirement: &2158958920 !ruby/object:Gem::Requirement
51
+ requirement: &2157568700 !ruby/object:Gem::Requirement
51
52
  none: false
52
53
  requirements:
53
54
  - - ! '>='
@@ -55,10 +56,10 @@ dependencies:
55
56
  version: 2.6.1
56
57
  type: :development
57
58
  prerelease: false
58
- version_requirements: *2158958920
59
+ version_requirements: *2157568700
59
60
  - !ruby/object:Gem::Dependency
60
61
  name: mocha
61
- requirement: &2158958500 !ruby/object:Gem::Requirement
62
+ requirement: &2157568240 !ruby/object:Gem::Requirement
62
63
  none: false
63
64
  requirements:
64
65
  - - ! '>='
@@ -66,10 +67,10 @@ dependencies:
66
67
  version: '0'
67
68
  type: :development
68
69
  prerelease: false
69
- version_requirements: *2158958500
70
+ version_requirements: *2157568240
70
71
  - !ruby/object:Gem::Dependency
71
72
  name: shoulda
72
- requirement: &2158957800 !ruby/object:Gem::Requirement
73
+ requirement: &2157567540 !ruby/object:Gem::Requirement
73
74
  none: false
74
75
  requirements:
75
76
  - - ! '>='
@@ -77,10 +78,10 @@ dependencies:
77
78
  version: '0'
78
79
  type: :development
79
80
  prerelease: false
80
- version_requirements: *2158957800
81
+ version_requirements: *2157567540
81
82
  - !ruby/object:Gem::Dependency
82
83
  name: sqlite3-ruby
83
- requirement: &2158957240 !ruby/object:Gem::Requirement
84
+ requirement: &2157566640 !ruby/object:Gem::Requirement
84
85
  none: false
85
86
  requirements:
86
87
  - - ! '>='
@@ -88,10 +89,10 @@ dependencies:
88
89
  version: '0'
89
90
  type: :development
90
91
  prerelease: false
91
- version_requirements: *2158957240
92
+ version_requirements: *2157566640
92
93
  - !ruby/object:Gem::Dependency
93
94
  name: sham_rack
94
- requirement: &2158956680 !ruby/object:Gem::Requirement
95
+ requirement: &2157565160 !ruby/object:Gem::Requirement
95
96
  none: false
96
97
  requirements:
97
98
  - - ! '>='
@@ -99,10 +100,10 @@ dependencies:
99
100
  version: '0'
100
101
  type: :development
101
102
  prerelease: false
102
- version_requirements: *2158956680
103
+ version_requirements: *2157565160
103
104
  - !ruby/object:Gem::Dependency
104
105
  name: capybara
105
- requirement: &2158956260 !ruby/object:Gem::Requirement
106
+ requirement: &2157545440 !ruby/object:Gem::Requirement
106
107
  none: false
107
108
  requirements:
108
109
  - - ! '>='
@@ -110,10 +111,10 @@ dependencies:
110
111
  version: '0'
111
112
  type: :development
112
113
  prerelease: false
113
- version_requirements: *2158956260
114
+ version_requirements: *2157545440
114
115
  - !ruby/object:Gem::Dependency
115
116
  name: crypt-isaac
116
- requirement: &2158955460 !ruby/object:Gem::Requirement
117
+ requirement: &2157544760 !ruby/object:Gem::Requirement
117
118
  none: false
118
119
  requirements:
119
120
  - - ! '>='
@@ -121,10 +122,10 @@ dependencies:
121
122
  version: '0'
122
123
  type: :development
123
124
  prerelease: false
124
- version_requirements: *2158955460
125
+ version_requirements: *2157544760
125
126
  - !ruby/object:Gem::Dependency
126
127
  name: launchy
127
- requirement: &2158954660 !ruby/object:Gem::Requirement
128
+ requirement: &2157543980 !ruby/object:Gem::Requirement
128
129
  none: false
129
130
  requirements:
130
131
  - - ! '>='
@@ -132,7 +133,7 @@ dependencies:
132
133
  version: '0'
133
134
  type: :development
134
135
  prerelease: false
135
- version_requirements: *2158954660
136
+ version_requirements: *2157543980
136
137
  description: CAS authentication module for Devise
137
138
  email: natbudin@gmail.com
138
139
  executables: []
@@ -155,6 +156,12 @@ files:
155
156
  - lib/devise_cas_authenticatable/model.rb
156
157
  - lib/devise_cas_authenticatable/routes.rb
157
158
  - lib/devise_cas_authenticatable/schema.rb
159
+ - lib/devise_cas_authenticatable/single_sign_out.rb
160
+ - lib/devise_cas_authenticatable/single_sign_out/session_store/active_record.rb
161
+ - lib/devise_cas_authenticatable/single_sign_out/session_store/redis.rb
162
+ - lib/devise_cas_authenticatable/single_sign_out/strategies.rb
163
+ - lib/devise_cas_authenticatable/single_sign_out/strategies/base.rb
164
+ - lib/devise_cas_authenticatable/single_sign_out/strategies/rails_cache.rb
158
165
  - lib/devise_cas_authenticatable/strategy.rb
159
166
  - rails/init.rb
160
167
  - spec/routes_spec.rb
@@ -203,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
210
  version: '0'
204
211
  segments:
205
212
  - 0
206
- hash: 3233699978426426317
213
+ hash: 3607945198939265999
207
214
  required_rubygems_version: !ruby/object:Gem::Requirement
208
215
  none: false
209
216
  requirements: