rubycas-client 2.3.8 → 2.3.9.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,8 +22,12 @@ class CasProxyCallbackController < ActionController::Base
22
22
  render :text => "Okay, the server is up, but please specify a pgtIou and pgtId." and return unless pgtIou and pgtId
23
23
 
24
24
  # TODO: pstore contents should probably be encrypted...
25
-
26
- casclient = CASClient::Frameworks::Rails::Filter.client
25
+
26
+ if Rails::VERSION::MAJOR > 2
27
+ casclient = RubyCAS::Filter.client
28
+ else
29
+ casclient = CASClient::Frameworks::Rails::Filter.client
30
+ end
27
31
 
28
32
  casclient.ticket_store.save_pgt_iou(pgtIou, pgtId)
29
33
 
@@ -218,7 +218,10 @@ module CASClient
218
218
  end
219
219
 
220
220
  def unauthorized!(controller, vr = nil)
221
- format = controller.request.format.to_sym
221
+ format = nil
222
+ unless controller.request.format.nil?
223
+ format = controller.request.format.to_sym
224
+ end
222
225
  format = (format == :js ? :json : format)
223
226
  case format
224
227
  when :xml, :json
@@ -2,39 +2,39 @@ module CASClient
2
2
  module XmlResponse
3
3
  attr_reader :xml, :parse_datetime
4
4
  attr_reader :failure_code, :failure_message
5
-
5
+
6
6
  def check_and_parse_xml(raw_xml)
7
7
  begin
8
- doc = REXML::Document.new(raw_xml)
8
+ doc = REXML::Document.new(raw_xml, :raw => :all)
9
9
  rescue REXML::ParseException => e
10
10
  raise BadResponseException,
11
11
  "MALFORMED CAS RESPONSE:\n#{raw_xml.inspect}\n\nEXCEPTION:\n#{e}"
12
12
  end
13
-
13
+
14
14
  unless doc.elements && doc.elements["cas:serviceResponse"]
15
15
  raise BadResponseException,
16
16
  "This does not appear to be a valid CAS response (missing cas:serviceResponse root element)!\nXML DOC:\n#{doc.to_s}"
17
17
  end
18
-
18
+
19
19
  return doc.elements["cas:serviceResponse"].elements[1]
20
20
  end
21
-
21
+
22
22
  def to_s
23
23
  xml.to_s
24
24
  end
25
25
  end
26
-
26
+
27
27
  # Represents a response from the CAS server to a 'validate' request
28
28
  # (i.e. after validating a service/proxy ticket).
29
29
  class ValidationResponse
30
30
  include XmlResponse
31
-
31
+
32
32
  attr_reader :protocol, :user, :pgt_iou, :proxies, :extra_attributes
33
-
33
+
34
34
  def initialize(raw_text, options={})
35
35
  parse(raw_text, options)
36
36
  end
37
-
37
+
38
38
  def parse(raw_text, options)
39
39
  raise BadResponseException,
40
40
  "CAS response is empty/blank." if raw_text.blank?
@@ -45,17 +45,17 @@ module CASClient
45
45
  @user = $~[2]
46
46
  return
47
47
  end
48
-
48
+
49
49
  @xml = check_and_parse_xml(raw_text)
50
-
50
+
51
51
  # if we got this far then we've got a valid XML response, so we're doing CAS 2.0
52
52
  @protocol = 2.0
53
-
53
+
54
54
  if is_success?
55
55
  cas_user = @xml.elements["cas:user"]
56
56
  @user = cas_user.text.strip if cas_user
57
57
  @pgt_iou = @xml.elements["cas:proxyGrantingTicket"].text.strip if @xml.elements["cas:proxyGrantingTicket"]
58
-
58
+
59
59
  proxy_els = @xml.elements.to_a('//cas:authenticationSuccess/cas:proxies/cas:proxy')
60
60
  if proxy_els.size > 0
61
61
  @proxies = []
@@ -63,13 +63,18 @@ module CASClient
63
63
  @proxies << el.text
64
64
  end
65
65
  end
66
-
66
+
67
67
  @extra_attributes = {}
68
68
  @xml.elements.to_a('//cas:authenticationSuccess/cas:attributes/* | //cas:authenticationSuccess/*[local-name() != \'proxies\' and local-name() != \'proxyGrantingTicket\' and local-name() != \'user\' and local-name() != \'attributes\']').each do |el|
69
69
  inner_text = el.cdatas.length > 0 ? el.cdatas.join('') : el.text
70
- @extra_attributes.merge! el.name => inner_text
70
+ name = el.name
71
+ unless (attrs = el.attributes).empty?
72
+ name = attrs['name']
73
+ inner_text = attrs['value']
74
+ end
75
+ @extra_attributes.merge! name => inner_text
71
76
  end
72
-
77
+
73
78
  # unserialize extra attributes
74
79
  @extra_attributes.each do |k, v|
75
80
  @extra_attributes[k] = parse_extra_attribute_value(v, options[:encode_extra_attributes_as])
@@ -85,26 +90,28 @@ module CASClient
85
90
 
86
91
  def parse_extra_attribute_value(value, encode_extra_attributes_as)
87
92
  attr_value = if value.blank?
88
- nil
89
- elsif !encode_extra_attributes_as
90
- begin
91
- YAML.load(value)
92
- rescue ArgumentError
93
- raise ArgumentError, "Did not find :encode_extra_attributes_as config parameter, hence default encoding scheme is YAML but CAS response recieved in encoded differently "
94
- end
95
- else
96
- if encode_extra_attributes_as == :json
97
- begin
98
- JSON.parse(value)
99
- rescue JSON::ParserError
100
- value
101
- end
102
- else
103
- YAML.load(value)
104
- end
105
- end
93
+ nil
94
+ elsif !encode_extra_attributes_as
95
+ begin
96
+ YAML.load(value)
97
+ rescue ArgumentError => e
98
+ raise ArgumentError, "Error parsing extra attribute with value #{value} as YAML: #{e}"
99
+ end
100
+ else
101
+ if encode_extra_attributes_as == :json
102
+ begin
103
+ JSON.parse(value)
104
+ rescue JSON::ParserError
105
+ value
106
+ end
107
+ elsif encode_extra_attributes_as == :raw
108
+ value
109
+ else
110
+ YAML.load(value)
111
+ end
112
+ end
106
113
 
107
- unless (attr_value.kind_of? Enumerable) || attr_value.nil?
114
+ unless attr_value.kind_of?(Enumerable) || attr_value.kind_of?(TrueClass) || attr_value.kind_of?(FalseClass) || attr_value.nil?
108
115
  attr_value.to_s
109
116
  else
110
117
  attr_value
@@ -114,30 +121,30 @@ module CASClient
114
121
  def is_success?
115
122
  (instance_variable_defined?(:@valid) && @valid) || (protocol > 1.0 && xml.name == "authenticationSuccess")
116
123
  end
117
-
124
+
118
125
  def is_failure?
119
126
  (instance_variable_defined?(:@valid) && !@valid) || (protocol > 1.0 && xml.name == "authenticationFailure" )
120
127
  end
121
128
  end
122
-
129
+
123
130
  # Represents a response from the CAS server to a proxy ticket request
124
131
  # (i.e. after requesting a proxy ticket).
125
132
  class ProxyResponse
126
133
  include XmlResponse
127
-
134
+
128
135
  attr_reader :proxy_ticket
129
-
136
+
130
137
  def initialize(raw_text, options={})
131
138
  parse(raw_text)
132
139
  end
133
-
140
+
134
141
  def parse(raw_text)
135
142
  raise BadResponseException,
136
143
  "CAS response is empty/blank." if raw_text.blank?
137
144
  @parse_datetime = Time.now
138
-
145
+
139
146
  @xml = check_and_parse_xml(raw_text)
140
-
147
+
141
148
  if is_success?
142
149
  @proxy_ticket = @xml.elements["cas:proxyTicket"].text.strip if @xml.elements["cas:proxyTicket"]
143
150
  elsif is_failure?
@@ -147,43 +154,43 @@ module CASClient
147
154
  # this should never happen, since the response should already have been recognized as invalid
148
155
  raise BadResponseException, "BAD CAS RESPONSE:\n#{raw_text.inspect}\n\nXML DOC:\n#{doc.inspect}"
149
156
  end
150
-
157
+
151
158
  end
152
-
159
+
153
160
  def is_success?
154
161
  xml.name == "proxySuccess"
155
162
  end
156
-
163
+
157
164
  def is_failure?
158
165
  xml.name == "proxyFailure"
159
166
  end
160
167
  end
161
-
168
+
162
169
  # Represents a response from the CAS server to a login request
163
170
  # (i.e. after submitting a username/password).
164
171
  class LoginResponse
165
172
  attr_reader :tgt, :ticket, :service_redirect_url
166
173
  attr_reader :failure_message
167
-
174
+
168
175
  def initialize(http_response = nil, options={})
169
176
  parse_http_response(http_response) if http_response
170
177
  end
171
-
178
+
172
179
  def parse_http_response(http_response)
173
180
  header = http_response.to_hash
174
-
181
+
175
182
  # FIXME: this regexp might be incorrect...
176
183
  if header['set-cookie'] &&
177
- header['set-cookie'].first &&
178
- header['set-cookie'].first =~ /tgt=([^&]+);/
184
+ header['set-cookie'].first &&
185
+ header['set-cookie'].first =~ /tgt=([^&]+);/
179
186
  @tgt = $~[1]
180
187
  end
181
-
188
+
182
189
  location = header['location'].first if header['location'] && header['location'].first
183
190
  if location =~ /ticket=([^&]+)/
184
191
  @ticket = $~[1]
185
192
  end
186
-
193
+
187
194
  if not ((http_response.kind_of?(Net::HTTPSuccess) || http_response.kind_of?(Net::HTTPFound)) && @ticket.present?)
188
195
  @failure = true
189
196
  # Try to extract the error message -- this only works with RubyCAS-Server.
@@ -195,19 +202,19 @@ module CASClient
195
202
  @failure_message = body
196
203
  end
197
204
  end
198
-
205
+
199
206
  @service_redirect_url = location
200
207
  end
201
-
208
+
202
209
  def is_success?
203
210
  !@failure && !ticket.blank?
204
211
  end
205
-
212
+
206
213
  def is_failure?
207
214
  @failure == true
208
215
  end
209
216
  end
210
-
217
+
211
218
  class BadResponseException < CASException
212
219
  end
213
220
  end
@@ -4,14 +4,16 @@ module CASClient
4
4
  class AbstractTicketStore
5
5
 
6
6
  attr_accessor :log
7
- @log = CASClient::LoggerWrapper.new
7
+ def log
8
+ @log ||= CASClient::LoggerWrapper.new
9
+ end
8
10
 
9
- def process_single_sign_out(si)
11
+ def process_single_sign_out(st)
10
12
 
11
- session_id, session = get_session_for_service_ticket(si)
13
+ session_id, session = get_session_for_service_ticket(st)
12
14
  if session
13
15
  session.destroy
14
- log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{si.inspect}.")
16
+ log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{st.inspect}.")
15
17
  else
16
18
  log.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.")
17
19
  end
@@ -19,16 +21,17 @@ module CASClient
19
21
  if session_id
20
22
  log.info("Single-sign-out for service ticket #{session_id.inspect} completed successfuly.")
21
23
  else
22
- log.debug("No session id found for CAS ticket #{si}")
24
+ log.debug("No session id found for CAS ticket #{st}")
23
25
  end
24
26
  end
25
27
 
26
28
  def get_session_for_service_ticket(st)
27
- session_id = read_service_session_lookup(si)
28
- if session_id
29
- session = ActiveRecord::SessionStore::Session.find_by_session_id(session_id)
29
+ session_id = read_service_session_lookup(st)
30
+ unless session_id.nil?
31
+ # This feels a bit hackish, but there isn't really a better way to go about it that I am aware of yet
32
+ session = ActiveRecord::SessionStore.session_class.find_by_session_id(session_id)
30
33
  else
31
- log.warn("Couldn't destroy session with SessionIndex #{si} because no corresponding session id could be looked up.")
34
+ log.warn("Couldn't destroy session service ticket #{st} because no corresponding session id could be found.")
32
35
  end
33
36
  [session_id, session]
34
37
  end
@@ -53,6 +56,12 @@ module CASClient
53
56
  def read_service_session_lookup(st)
54
57
  raise 'Implement this in a subclass!'
55
58
  end
59
+
60
+ def session_id_from_controller(controller)
61
+ session_id = controller.request.session_options[:id] || controller.session.session_id
62
+ raise CASClient::CASException, "Failed to extract session_id from controller" if session_id.nil?
63
+ session_id
64
+ end
56
65
  end
57
66
 
58
67
  # A Ticket Store that keeps it's tickets in a directory on the local filesystem.
@@ -83,10 +92,10 @@ module CASClient
83
92
  # Rails session id.
84
93
  # Returns the filename of the lookup file created.
85
94
  def store_service_session_lookup(st, controller)
86
- raise CASException, "No service_ticket specified." unless st
87
- raise CASException, "No controller specified." unless controller
95
+ raise CASException, "No service_ticket specified." if st.nil?
96
+ raise CASException, "No controller specified." if controller.nil?
88
97
 
89
- sid = controller.request.session_options[:id] || controller.session.session_id
98
+ sid = session_id_from_controller(controller)
90
99
 
91
100
  st = st.ticket if st.kind_of? ServiceTicket
92
101
  f = File.new(filename_of_service_session_lookup(st), 'w')
@@ -100,11 +109,11 @@ module CASClient
100
109
  # cas_sess.<session ticket> file created in a prior call to
101
110
  # #store_service_session_lookup.
102
111
  def read_service_session_lookup(st)
103
- raise CASException, "No service_ticket specified." unless st
112
+ raise CASException, "No service_ticket specified." if st.nil?
104
113
 
105
114
  st = st.ticket if st.kind_of? ServiceTicket
106
115
  ssl_filename = filename_of_service_session_lookup(st)
107
- return File.exists?(ssl_filename) && IO.read(ssl_filename)
116
+ return IO.read(ssl_filename) if File.exists?(ssl_filename)
108
117
  end
109
118
 
110
119
  # Removes a stored relationship between a ServiceTicket and a local
@@ -113,7 +122,7 @@ module CASClient
113
122
  #
114
123
  # See #store_service_session_lookup.
115
124
  def cleanup_service_session_lookup(st)
116
- raise CASException, "No service_ticket specified." unless st
125
+ raise CASException, "No service_ticket specified." if st.nil?
117
126
 
118
127
  st = st.ticket if st.kind_of? ServiceTicket
119
128
  ssl_filename = filename_of_service_session_lookup(st)
@@ -121,6 +130,9 @@ module CASClient
121
130
  end
122
131
 
123
132
  def save_pgt_iou(pgt_iou, pgt)
133
+ raise CASException, "Invalid pgt_iou" if pgt_iou.nil?
134
+ raise CASException, "Invalid pgt" if pgt.nil?
135
+
124
136
  # TODO: pstore contents should probably be encrypted...
125
137
  pstore = open_pstore
126
138
 
@@ -135,17 +147,14 @@ module CASClient
135
147
  pstore = open_pstore
136
148
 
137
149
  pgt = nil
150
+ # TODO: need to periodically clean the storage, otherwise it will just keep growing
138
151
  pstore.transaction do
139
152
  pgt = pstore[pgt_iou]
153
+ pstore.delete pgt_iou
140
154
  end
141
155
 
142
156
  raise CASException, "Invalid pgt_iou specified. Perhaps this pgt has already been retrieved?" unless pgt
143
157
 
144
- # TODO: need to periodically clean the storage, otherwise it will just keep growing
145
- pstore.transaction do
146
- pstore.delete pgt_iou
147
- end
148
-
149
158
  pgt
150
159
  end
151
160
 
@@ -19,25 +19,30 @@ module CASClient
19
19
  end
20
20
 
21
21
  def store_service_session_lookup(st, controller)
22
- #get the session from the rack env using ActiveRecord::SessionStore::SESSION_RECORD_KEY = 'rack.session.record'
22
+ raise CASException, "No service_ticket specified." unless st
23
+ raise CASException, "No controller specified." unless controller
23
24
 
24
25
  st = st.ticket if st.kind_of? ServiceTicket
25
26
  session = controller.session
26
27
  session[:service_ticket] = st
27
28
  end
28
29
 
29
- def get_session_for_service_ticket(st)
30
+ def read_service_session_lookup(st)
31
+ raise CASException, "No service_ticket specified." unless st
30
32
  st = st.ticket if st.kind_of? ServiceTicket
31
33
  session = ActiveRecord::SessionStore::Session.find_by_service_ticket(st)
32
- session_id = session ? session.session_id : nil
33
- [session_id, session]
34
+ session ? session.session_id : nil
34
35
  end
35
36
 
36
37
  def cleanup_service_session_lookup(st)
37
38
  #no cleanup needed for this ticket store
39
+ #we still raise the exception for API compliance
40
+ raise CASException, "No service_ticket specified." unless st
38
41
  end
39
42
 
40
43
  def save_pgt_iou(pgt_iou, pgt)
44
+ raise CASClient::CASException.new("Invalid pgt_iou") if pgt_iou.nil?
45
+ raise CASClient::CASException.new("Invalid pgt") if pgt.nil?
41
46
  pgtiou = CasPgtiou.create(:pgt_iou => pgt_iou, :pgt_id => pgt)
42
47
  end
43
48
 
@@ -45,9 +50,9 @@ module CASClient
45
50
  raise CASException, "No pgt_iou specified. Cannot retrieve the pgt." unless pgt_iou
46
51
 
47
52
  pgtiou = CasPgtiou.find_by_pgt_iou(pgt_iou)
48
- pgt = pgtiou.pgt_id
49
53
 
50
- raise CASException, "Invalid pgt_iou specified. Perhaps this pgt has already been retrieved?" unless pgt
54
+ raise CASException, "Invalid pgt_iou specified. Perhaps this pgt has already been retrieved?" unless pgtiou
55
+ pgt = pgtiou.pgt_id
51
56
 
52
57
  pgtiou.destroy
53
58