rubycas-client 2.3.8 → 2.3.9.rc1

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.
@@ -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