cie-es 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +19 -0
  5. data/README.md +126 -0
  6. data/Rakefile +41 -0
  7. data/cie-es.gemspec +22 -0
  8. data/lib/cie/ruby-saml/authrequest.rb +205 -0
  9. data/lib/cie/ruby-saml/coding.rb +34 -0
  10. data/lib/cie/ruby-saml/error_handling.rb +27 -0
  11. data/lib/cie/ruby-saml/logging.rb +26 -0
  12. data/lib/cie/ruby-saml/logout_request.rb +126 -0
  13. data/lib/cie/ruby-saml/logout_response.rb +132 -0
  14. data/lib/cie/ruby-saml/metadata.rb +489 -0
  15. data/lib/cie/ruby-saml/request.rb +81 -0
  16. data/lib/cie/ruby-saml/response.rb +678 -0
  17. data/lib/cie/ruby-saml/settings.rb +89 -0
  18. data/lib/cie/ruby-saml/utils.rb +225 -0
  19. data/lib/cie/ruby-saml/validation_error.rb +7 -0
  20. data/lib/cie/ruby-saml/version.rb +5 -0
  21. data/lib/cie/xml_security.rb +166 -0
  22. data/lib/cie/xml_security_new.rb +373 -0
  23. data/lib/cie-es.rb +14 -0
  24. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  25. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  26. data/lib/schemas/xenc_schema.xsd +146 -0
  27. data/lib/schemas/xmldsig_schema.xsd +318 -0
  28. data/test/certificates/certificate1 +12 -0
  29. data/test/logoutrequest_test.rb +98 -0
  30. data/test/request_test.rb +53 -0
  31. data/test/response_test.rb +219 -0
  32. data/test/responses/adfs_response_sha1.xml +46 -0
  33. data/test/responses/adfs_response_sha256.xml +46 -0
  34. data/test/responses/adfs_response_sha384.xml +46 -0
  35. data/test/responses/adfs_response_sha512.xml +46 -0
  36. data/test/responses/no_signature_ns.xml +48 -0
  37. data/test/responses/open_saml_response.xml +56 -0
  38. data/test/responses/response1.xml.base64 +1 -0
  39. data/test/responses/response2.xml.base64 +79 -0
  40. data/test/responses/response3.xml.base64 +66 -0
  41. data/test/responses/response4.xml.base64 +93 -0
  42. data/test/responses/response5.xml.base64 +102 -0
  43. data/test/responses/response_with_ampersands.xml +139 -0
  44. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  45. data/test/responses/simple_saml_php.xml +71 -0
  46. data/test/responses/wrapped_response_2.xml.base64 +150 -0
  47. data/test/settings_test.rb +43 -0
  48. data/test/test_helper.rb +65 -0
  49. data/test/xml_security_test.rb +123 -0
  50. metadata +119 -0
@@ -0,0 +1,678 @@
1
+ require_relative "../xml_security_new"
2
+ require "time"
3
+ require "nokogiri"
4
+ require "base64"
5
+ require "openssl"
6
+ require "digest/sha1"
7
+ require_relative "utils"
8
+
9
+ # Only supports SAML 2.0
10
+ module Cie
11
+ module Saml
12
+
13
+ class Response
14
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
15
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
16
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
17
+
18
+ attr_accessor :options, :response, :document, :settings, :attr_name_format
19
+ attr_reader :decrypted_document
20
+
21
+
22
+ def initialize(response, options = {})
23
+ raise ArgumentError.new("Response cannot be nil") if response.nil?
24
+ self.options = options
25
+ self.response = response
26
+ if assertion_encrypted?
27
+ @decrypted_document = generate_decrypted_document
28
+ end
29
+ begin
30
+ self.document = Cie::XMLSecurityNew::SignedDocument.new(Base64.decode64(response))
31
+ rescue REXML::ParseException => e
32
+ if response =~ /</
33
+ self.document = Cie::XMLSecurityNew::SignedDocument.new(response)
34
+ else
35
+ raise e
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ # Checks if the SAML Response contains or not an EncryptedAssertion element
42
+ # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
43
+ #
44
+ def assertion_encrypted?
45
+ false
46
+ #!REXML::XPath.first(self.document, "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", { "p" => PROTOCOL, "a" => ASSERTION }).nil?
47
+ end
48
+
49
+ def is_valid?
50
+ validate
51
+ end
52
+
53
+ def validate!
54
+ validate(false)
55
+ end
56
+
57
+ # The value of the user identifier as designated by the initialization request response
58
+ def name_id
59
+ @name_id ||= begin
60
+ node = REXML::XPath.first(document, "/saml2p:Response/saml2:Assertion[@ID='#{document.signed_element_id}']/saml2:Subject/saml2:NameID")
61
+ node ||= REXML::XPath.first(document, "/saml2p:Response[@ID='#{document.signed_element_id}']/saml2:Assertion/saml2:Subject/saml2:NameID")
62
+ node.nil? ? nil : node.text
63
+ end
64
+ end
65
+
66
+
67
+
68
+
69
+ # A hash of alle the attributes with the response. Assuming there is only one value for each key
70
+ def attributes
71
+ @attr_statements ||= begin
72
+ result = {}
73
+ stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
74
+ return {} if stmt_element.nil?
75
+
76
+ @attr_name_format = []
77
+ stmt_element.elements.each do |attr_element|
78
+ name = attr_element.attributes["Name"]
79
+ #salvo i vari format per controllare poi che non ce ne siano di null
80
+ @attr_name_format << attr_element.attributes["NameFormat"].blank? ? nil : attr_element.attributes["NameFormat"].text
81
+ value = (attr_element.elements.blank? ? nil : attr_element.elements.first.text)
82
+
83
+ result[name] = value
84
+ end
85
+ #mette il symbol
86
+ result.keys.each do |key|
87
+ result[key.intern] = result[key]
88
+ end
89
+
90
+ result
91
+ end
92
+ end
93
+
94
+ # When this user session should expire at latest
95
+ def session_expires_at
96
+ @expires_at ||= begin
97
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
98
+ parse_time(node, "SessionNotOnOrAfter")
99
+ end
100
+ end
101
+
102
+
103
+
104
+ # Checks the status of the response for a "Success" code
105
+ def success?
106
+ @status_code ||= begin
107
+ node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
108
+ node.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success" unless node.blank?
109
+ end
110
+ end
111
+
112
+ # Ritorno il valore dello StatusMessage
113
+ def get_status_message
114
+ node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION })
115
+ node.text unless node.blank?
116
+ end
117
+
118
+ # Conditions (if any) for the assertion to run
119
+ def conditions
120
+ @conditions ||= begin
121
+ REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
122
+ end
123
+ end
124
+
125
+
126
+
127
+ #metodi per ricavare info per tracciatura agid
128
+
129
+
130
+ def issuer
131
+ @issuer ||= begin
132
+ node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
133
+ node ||= REXML::XPath.first(document, "/p:Response/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
134
+ node.nil? ? nil : node.text
135
+ end
136
+ end
137
+
138
+ # Gets the Issuers (from Response and Assertion).
139
+ # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
140
+ # @return [Array] Array with the Issuers (REXML::Element)
141
+ #
142
+ def issuers(soft=true)
143
+ @issuers ||= begin
144
+ issuer_response_nodes = REXML::XPath.match(
145
+ document,
146
+ "/p:Response/a:Issuer",
147
+ { "p" => PROTOCOL, "a" => ASSERTION }
148
+ )
149
+
150
+ unless issuer_response_nodes.size == 1
151
+ # error_msg = "Issuer of the Response not found or multiple."
152
+ # raise ValidationError.new(error_msg)
153
+ return (soft ? false : validation_error("Issuer of the Response not found or multiple."))
154
+ end
155
+
156
+ issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
157
+ unless issuer_assertion_nodes.size == 1
158
+ # error_msg = "Issuer of the Assertion not found or multiple."
159
+ # raise ValidationError.new(error_msg)
160
+ return (soft ? false : validation_error("Issuer of the Assertion not found or multiple."))
161
+ end
162
+
163
+ issuer_response_nodes.each{ |iss|
164
+ #controllo: L'attributo Format di Issuer deve essere presente con il valore urn:oasis:names:tc:SAML:2.0:nameid-format:entity
165
+ return (soft ? false : validation_error("Elemento Issuer non ha formato corretto ")) if !iss.attributes['Format'].nil? && iss.attributes['Format'] != 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
166
+
167
+ }
168
+
169
+ issuer_assertion_nodes.each{ |iss|
170
+ #controllo: L'attributo Format di Issuer deve essere presente con il valore urn:oasis:names:tc:SAML:2.0:nameid-format:entity
171
+ return (soft ? false : validation_error("Elemento Issuer non ha formato corretto ")) if iss.attributes['Format'] != 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
172
+
173
+ }
174
+
175
+ nodes = issuer_response_nodes + issuer_assertion_nodes
176
+
177
+ nodes.map { |node| Utils.element_text(node) }.compact.uniq
178
+ end
179
+ end
180
+
181
+
182
+
183
+ def response_to_id
184
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
185
+ return node.attributes["InResponseTo"] unless node.blank?
186
+ end
187
+
188
+ def id
189
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
190
+ return node.attributes["ID"] unless node.blank?
191
+ end
192
+
193
+ def issue_instant
194
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
195
+ return node.attributes["IssueInstant"] unless node.blank?
196
+ end
197
+
198
+ def assertion_present?
199
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
200
+ return !node.blank?
201
+ end
202
+
203
+ def assertion_issue_instant
204
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
205
+ return node.attributes["IssueInstant"] unless node.blank?
206
+ end
207
+
208
+ def assertion_id
209
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
210
+ return node.attributes["ID"] unless node.blank?
211
+ end
212
+
213
+ def assertion_subject
214
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
215
+ return node.text
216
+ end
217
+
218
+ def assertion_subject_name_qualifier
219
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
220
+ return node.attributes["NameQualifier"] unless node.blank?
221
+ end
222
+
223
+ def assertion_subject_confirmation_data_not_on_or_after
224
+ node_subj_conf_data = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation/a:SubjectConfirmationData')
225
+ return node_subj_conf_data.attributes["NotOnOrAfter"] unless node_subj_conf_data.blank?
226
+ end
227
+
228
+ def assertion_conditions_not_before
229
+ node_cond_not_before = xpath_first_from_signed_assertion('/a:Conditions')
230
+ return node_cond_not_before.attributes["NotBefore"] unless node_cond_not_before.blank?
231
+ end
232
+
233
+ def assertion_conditions_not_on_or_after
234
+ node_cond_not_on_or_after = xpath_first_from_signed_assertion('/a:Conditions')
235
+ return node_cond_not_on_or_after.attributes["NotOnOrAfter"] unless node_cond_not_on_or_after.blank?
236
+ end
237
+
238
+ private
239
+
240
+ def validation_error(message)
241
+ raise ValidationError.new(message)
242
+ end
243
+
244
+ def validate(soft = true)
245
+ # prime the IdP metadata before the document validation.
246
+ # The idp_cert needs to be populated before the validate_response_state method
247
+
248
+ if settings
249
+ idp_metadata = Cie::Saml::Metadata.new(settings).get_idp_metadata
250
+ end
251
+
252
+ #carico nei setting l'idp_entity_id
253
+ entity_descriptor_element = REXML::XPath.first(idp_metadata,"/EntityDescriptor")
254
+ if !entity_descriptor_element.nil?
255
+ settings.idp_entity_id = entity_descriptor_element.attributes["entityID"]
256
+ end
257
+
258
+ return false if validate_structure(soft) == false
259
+ return false if validate_response_state(soft) == false
260
+ return false if validate_conditions(soft) == false
261
+ #validazione assertion firmata
262
+ return false if validate_signed_elements(soft) == false
263
+ #validazione version che sia 2.0
264
+ return false if validate_version(soft) == false
265
+ #validazione version delle asserzioni che sia 2.0
266
+ return false if validate_version_assertion(soft) == false
267
+ #validazione destination
268
+ return false if validate_destination(soft) == false
269
+ #validazione status
270
+ return false if validate_status(soft) == false
271
+ #validazione issuer
272
+ return false if validate_issuer(soft) == false
273
+ #validazioni varie su asserzioni
274
+ return false if validate_assertion(soft) == false
275
+ #validazione presenza format su attributes
276
+ return false if validate_name_format_attributes(soft) == false
277
+
278
+
279
+ # Just in case a user needs to toss out the signature validation,
280
+ # I'm adding in an option for it. (Sometimes canonicalization is a bitch!)
281
+ return true if settings.skip_validation == true
282
+
283
+ # document.validte populates the idp_cert
284
+ return false if document.validate_document(get_fingerprint, soft) == false
285
+
286
+ # validate response code
287
+ return false if success? == false
288
+
289
+ return true
290
+ end
291
+
292
+ # Validates the Issuer (Of the SAML Response and the SAML Assertion)
293
+ # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
294
+ # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
295
+ # @raise [ValidationError] if soft == false and validation fails
296
+ #
297
+ def validate_issuer(soft=true)
298
+ obtained_issuers = issuers(soft)
299
+ if obtained_issuers == false
300
+ return false #errori all'interno del metodo issuers
301
+ else
302
+ obtained_issuers.each do |iss|
303
+
304
+ unless Cie::Saml::Utils.uri_match?(iss, settings.idp_entity_id)
305
+ # error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
306
+ # return append_error(error_msg)
307
+ return (soft ? false : validation_error("Elemento Issuer diverso da EntityID IdP, expected: <#{settings.idp_entity_id}>, but was: <#{iss}>"))
308
+ end
309
+ end
310
+
311
+ true
312
+ end
313
+ end
314
+
315
+
316
+
317
+ #validate status e status code
318
+ def validate_status(soft=true)
319
+ #controlli su status
320
+ node_status = REXML::XPath.first(document, "/p:Response/p:Status", { "p" => PROTOCOL, "a" => ASSERTION })
321
+ return (soft ? false : validation_error("Status non presente")) if node_status.blank?
322
+ #controlli su status code
323
+ node_status_code = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
324
+ return (soft ? false : validation_error("Status code non presente")) if node_status_code.blank?
325
+ return (soft ? false : validation_error("Status non presente")) unless node_status_code.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success"
326
+ true
327
+ end
328
+
329
+
330
+
331
+ # Validates the SAML version (2.0)
332
+ # If fails, the error is added to the errors array.
333
+ # @return [Boolean] True if the SAML Response is 2.0, otherwise returns False
334
+ #
335
+ def version(document)
336
+ @version ||= begin
337
+ node = REXML::XPath.first(
338
+ document,
339
+ "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
340
+ { "p" => PROTOCOL }
341
+ )
342
+ node.nil? ? nil : node.attributes['Version']
343
+ end
344
+ end
345
+
346
+ def version_assertion(document)
347
+ assertion_nodes = xpath_from_signed_assertion()
348
+ @version_assertion = "2.0"
349
+ #ciclo sui nodi delle asserzioni, se uno ha una versione diversa da 2.0 ritorno nil
350
+ unless assertion_nodes.blank?
351
+ assertion_nodes.each{ |ass_node|
352
+ return nil if ass_node.attributes['Version'] != "2.0"
353
+ }
354
+ end
355
+ @version_assertion
356
+ end
357
+
358
+ def validate_version(soft = true)
359
+ unless version(self.document) == "2.0"
360
+ #return append_error("Unsupported SAML version")
361
+ return soft ? false : validation_error("Unsupported SAML version")
362
+ end
363
+ true
364
+ end
365
+
366
+ def validate_version_assertion(soft = true)
367
+ unless version_assertion(self.document) == "2.0"
368
+ #return append_error("Unsupported SAML version")
369
+ return soft ? false : validation_error("Unsupported SAML Assertion version")
370
+ end
371
+ true
372
+ end
373
+
374
+ def validate_signed_elements(soft = true)
375
+ signature_nodes = REXML::XPath.match(decrypted_document.nil? ? document : decrypted_document,"//ds:Signature",{"ds"=>DSIG})
376
+ signed_elements = []
377
+ verified_seis = []
378
+ verified_ids = []
379
+ signature_nodes.each do |signature_node|
380
+ signed_element = signature_node.parent.name
381
+ if signed_element != 'Response' && signed_element != 'Assertion'
382
+ return soft ? false : validation_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
383
+ #return append_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
384
+ end
385
+
386
+ if signature_node.parent.attributes['ID'].nil?
387
+ return soft ? false : validation_error("Signed Element must contain an ID. SAML Response rejected")
388
+ #return append_error("Signed Element must contain an ID. SAML Response rejected")
389
+ end
390
+
391
+ id = signature_node.parent.attributes.get_attribute("ID").value
392
+ if verified_ids.include?(id)
393
+ return soft ? false : validation_error("Duplicated ID. SAML Response rejected")
394
+ #return append_error("Duplicated ID. SAML Response rejected")
395
+ end
396
+ verified_ids.push(id)
397
+
398
+ # Check that reference URI matches the parent ID and no duplicate References or IDs
399
+ ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
400
+ if ref
401
+ uri = ref.attributes.get_attribute("URI")
402
+ if uri && !uri.value.empty?
403
+ sei = uri.value[1..-1]
404
+
405
+ unless sei == id
406
+ #return append_error("Found an invalid Signed Element. SAML Response rejected")
407
+ return soft ? false : validation_error("Found an invalid Signed Element. SAML Response rejected")
408
+ end
409
+
410
+ if verified_seis.include?(sei)
411
+ #return append_error("Duplicated Reference URI. SAML Response rejected")
412
+ return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
413
+ end
414
+
415
+ verified_seis.push(sei)
416
+ end
417
+ end
418
+
419
+ signed_elements << signed_element
420
+ end
421
+
422
+ unless signature_nodes.length < 3 && !signed_elements.empty?
423
+ #return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
424
+ return soft ? false : validation_error("Found an unexpected number of Signature Element. SAML Response rejected")
425
+ end
426
+
427
+ #if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
428
+ if !(signed_elements.include? "Assertion")
429
+ #return append_error("The Assertion of the Response is not signed and the SP requires it")
430
+ return soft ? false : validation_error("L'asserzione non è firmata.")
431
+ end
432
+
433
+ true
434
+ end
435
+
436
+ def validate_structure(soft = true)
437
+ Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
438
+ @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
439
+ @xml = Nokogiri::XML(self.document.to_s)
440
+ end
441
+ if soft
442
+ @schema.validate(@xml).map{ return false }
443
+ else
444
+ @schema.validate(@xml).map{ |error| raise(Exception.new("#{error.message}\n\n#{@xml.to_s}")) }
445
+ end
446
+ end
447
+
448
+ def validate_response_state(soft = true)
449
+ if response.empty?
450
+ return soft ? false : validation_error("Blank response")
451
+ end
452
+
453
+ if settings.nil?
454
+ return soft ? false : validation_error("No settings on response")
455
+ end
456
+
457
+ if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
458
+ return soft ? false : validation_error("No fingerprint or certificate on settings")
459
+ end
460
+
461
+ true
462
+ end
463
+
464
+ # Validates the Destination, (If the SAML Response is received where expected).
465
+ # If the response was initialized with the :skip_destination option, this validation is skipped,
466
+ # If fails, the error is added to the errors array
467
+ # @return [Boolean] True if there is a Destination element that matches the Consumer Service URL, otherwise False
468
+
469
+ # @return [String|nil] Destination attribute from the SAML Response.
470
+ #
471
+ def destination
472
+ @destination ||= begin
473
+ node = REXML::XPath.first(
474
+ document,
475
+ "/p:Response",
476
+ { "p" => PROTOCOL }
477
+ )
478
+ node.nil? ? nil : node.attributes['Destination']
479
+ end
480
+ end
481
+
482
+ def validate_destination(soft = true)
483
+ return (soft ? false : validation_error("La response non ha destination")) if destination.nil?
484
+ #return true if options[:skip_destination]
485
+
486
+ if destination.empty?
487
+ # error_msg = "The response has an empty Destination value"
488
+ # return append_error(error_msg)
489
+ return soft ? false : validation_error("The response has an empty Destination value")
490
+ end
491
+
492
+ return true if settings.assertion_consumer_service_url.nil? || settings.assertion_consumer_service_url.empty?
493
+
494
+ unless Cie::Saml::Utils.uri_match?(destination, settings.assertion_consumer_service_url)
495
+ # error_msg = "The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}"
496
+ # return append_error(error_msg)
497
+ return soft ? false : validation_error("The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}")
498
+ end
499
+
500
+ true
501
+ end
502
+
503
+ def validate_assertion(soft = true)
504
+ #posso avere n nodi asserzione..forse
505
+ nodes_assertion = xpath_from_signed_assertion
506
+ unless nodes_assertion.blank?
507
+ #Elemento NameID non specificato
508
+ node_name_id = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
509
+ unless node_name_id.blank?
510
+ return soft ? false : validation_error("Errore su Asserzione: NameID vuoto") if node_name_id.text.blank?
511
+ #controlli su attributo format
512
+ attr_format = node_name_id.attribute("Format")
513
+ return soft ? false : validation_error("Errore su Asserzione: Format su NameID vuoto") if attr_format.blank? || attr_format.to_s != "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" #45
514
+ #controlli su attributo NameQualifier
515
+ attr_name_qual = node_name_id.attribute("NameQualifier")
516
+ return soft ? false : validation_error("Errore su Asserzione: NameQualifier su NameID vuoto") if attr_name_qual.blank? || ( !attr_name_qual.blank? && attr_name_qual.value.blank?)#48 e 49
517
+ else
518
+ return soft ? false : validation_error("Errore su Asserzione: NameID non presente")
519
+
520
+ end
521
+ #Controlli su SubjectConfirmation
522
+ node_subj_conf = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation')
523
+ unless node_subj_conf.blank?
524
+ #controlli su attributo Method
525
+ attr_method = node_subj_conf.attribute("Method")
526
+ return soft ? false : validation_error("Errore su Asserzione: Method su SubjectConfirmation vuoto") if attr_method.blank? || attr_method.to_s != "urn:oasis:names:tc:SAML:2.0:cm:bearer" #53 54 e 55
527
+ #Controlli su SubjectConfirmationData
528
+ node_subj_conf_data = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation/a:SubjectConfirmationData')
529
+ unless node_subj_conf_data.blank?
530
+ #controllo attr Recipient, vuoto o diverso da AssertionConsumerServiceURL
531
+ attr_recipient = node_subj_conf_data.attribute("Recipient")
532
+ return soft ? false : validation_error("Errore su Asserzione: Recipient su SubjectConfirmationData vuoto o diverso da AssertionConsumerServiceURL") if attr_recipient.blank? || attr_recipient.to_s != settings.assertion_consumer_service_url #57 58 e 59
533
+ #controllo attr InResponseTo, vuoto o diverso da ID request
534
+ node_response = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL})
535
+ id_request = node_response.attribute("InResponseTo")
536
+ attr_in_resp_to = node_subj_conf_data.attribute("InResponseTo")
537
+ return soft ? false : validation_error("Errore su Asserzione: InResponseTo su SubjectConfirmationData vuoto o diverso da ID request") if attr_in_resp_to.blank? || attr_in_resp_to.to_s != id_request.to_s #57 58 e 59
538
+
539
+ #controllo attr NotOnOrAfter se vuoto o non presente #63 64
540
+ attr_not_on_or_after = node_subj_conf_data.attribute("NotOnOrAfter")
541
+ return soft ? false : validation_error("Errore su Asserzione: NotOnOrAfter su SubjectConfirmationData mancante") if attr_not_on_or_after.blank?
542
+
543
+
544
+ else
545
+ return soft ? false : validation_error("Errore su Asserzione: SubjectConfirmationData non presente")
546
+ end
547
+
548
+ else
549
+ return soft ? false : validation_error("Errore su Asserzione: SubjectConfirmation non presente")
550
+ end
551
+
552
+ #Controlli su Conditions
553
+ node_conditions = xpath_first_from_signed_assertion('/a:Conditions')
554
+ unless node_conditions.blank?
555
+ attr_not_before = node_conditions.attribute("NotBefore")
556
+ return soft ? false : validation_error("Errore su Asserzione: Recipient su SubjectConfirmationData vuoto") if attr_not_before.blank? #75 76
557
+ #83 84. Assertion - Elemento AudienceRestriction di Condition mancante
558
+ node_conditions_audience_restrictions = xpath_first_from_signed_assertion('/a:Conditions/a:AudienceRestriction')
559
+ return soft ? false : validation_error("Errore su Asserzione: AudienceRestriction su Conditions vuoto") if node_conditions_audience_restrictions.blank? #83 84
560
+ #85 86 87. Assertion - Elemento Audience di AudienceRestriction mancante
561
+ node_conditions_audience_restrictions_audience = xpath_first_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
562
+ #Cieer.logger.error "\n\n node_conditions_audience_restrictions_audience #{node_conditions_audience_restrictions_audience}"
563
+ #Cieer.logger.error "\n\n settings.issuer #{settings.issuer}"
564
+ return soft ? false : validation_error("Errore su Asserzione: Audience su AudienceRestriction vuoto") if node_conditions_audience_restrictions_audience.blank? || node_conditions_audience_restrictions_audience.text != settings.issuer #83 84
565
+ else
566
+ return soft ? false : validation_error("Errore su Asserzione: Conditions non presente")
567
+ end
568
+
569
+
570
+ node_auth_stat_context_class_ref = xpath_first_from_signed_assertion('/a:AuthnStatement/a:AuthnContext/a:AuthnContextClassRef')
571
+ #Cieer.logger.error "\n\n node_auth_stat_context_class_ref #{node_auth_stat_context_class_ref.text}"
572
+ return soft ? false : validation_error("Errore su Asserzione: AuthnContextClassRef di AuthnContext su AuthnStatement vuoto o non L2") if node_auth_stat_context_class_ref.blank? || ( (node_auth_stat_context_class_ref.text != 'https://www.spid.gov.it/SpidL2') && (node_auth_stat_context_class_ref.text != 'https://www.spid.gov.it/SpidL3'))
573
+
574
+ node_attr_stmt_attribute_value = xpath_first_from_signed_assertion("/a:AttributeStatement/a:Attribute/a:AttributeValue")
575
+ #Elemento AttributeStatement presente, ma sottoelemento Attribute non specificato, caso 99
576
+ return soft ? false : validation_error("Errore su Asserzione: AttributeValue di Attribute su AttributeStatement vuoto") if node_attr_stmt_attribute_value.blank?
577
+
578
+
579
+ else
580
+ return soft ? false : validation_error("Errore su Asserzione: non presente")
581
+ end
582
+ true
583
+ end
584
+
585
+
586
+ def validate_name_format_attributes(soft=true)
587
+ unless attributes.blank?
588
+ return false if @attr_name_format.blank? || (@attr_name_format.length != (attributes.length / 2))
589
+ end
590
+ true
591
+ end
592
+
593
+ def get_fingerprint
594
+ idp_metadata = Cie::Saml::Metadata.new(settings).get_idp_metadata
595
+
596
+ if settings.idp_cert
597
+ cert_text = Base64.decode64(settings.idp_cert)
598
+ cert = OpenSSL::X509::Certificate.new(cert_text)
599
+ Digest::SHA2.hexdigest(cert.to_der).upcase.scan(/../).join(":")
600
+ else
601
+ settings.idp_cert_fingerprint
602
+ end
603
+
604
+ end
605
+
606
+ def validate_conditions(soft = true)
607
+ return true if conditions.nil?
608
+ return true if options[:skip_conditions]
609
+
610
+ if not_before = parse_time(conditions, "NotBefore")
611
+ if Time.now.utc < not_before
612
+ return soft ? false : validation_error("Current time is earlier than NotBefore condition")
613
+ end
614
+ end
615
+
616
+ if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
617
+ if Time.now.utc >= not_on_or_after
618
+ return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
619
+ end
620
+ end
621
+
622
+ true
623
+ end
624
+
625
+ def parse_time(node, attribute)
626
+ if node && node.attributes[attribute]
627
+ Time.parse(node.attributes[attribute])
628
+ end
629
+ end
630
+
631
+ # Extracts all the appearances that matchs the subelt (pattern)
632
+ # Search on any Assertion that is signed, or has a Response parent signed
633
+ # @param subelt [String] The XPath pattern
634
+ # @return [Array of REXML::Element] Return all matches
635
+ #
636
+ def xpath_from_signed_assertion(subelt=nil)
637
+ doc = decrypted_document.nil? ? document : decrypted_document
638
+ node = REXML::XPath.match(
639
+ doc,
640
+ "/p:Response/a:Assertion[@ID=$id]#{subelt}",
641
+ { "p" => PROTOCOL, "a" => ASSERTION },
642
+ { 'id' => doc.signed_element_id }
643
+ )
644
+ node.concat( REXML::XPath.match(
645
+ doc,
646
+ "/p:Response[@ID=$id]/a:Assertion#{subelt}",
647
+ { "p" => PROTOCOL, "a" => ASSERTION },
648
+ { 'id' => doc.signed_element_id }
649
+ ))
650
+ end
651
+
652
+ # Extracts the first appearance that matchs the subelt (pattern)
653
+ # Search on any Assertion that is signed, or has a Response parent signed
654
+ # @param subelt [String] The XPath pattern
655
+ # @return [REXML::Element | nil] If any matches, return the Element
656
+ #
657
+ def xpath_first_from_signed_assertion(subelt=nil)
658
+ doc = decrypted_document.nil? ? document : decrypted_document
659
+ node = REXML::XPath.first(
660
+ doc,
661
+ "/p:Response/a:Assertion[@ID=$id]#{subelt}",
662
+ { "p" => PROTOCOL, "a" => ASSERTION },
663
+ { 'id' => doc.signed_element_id }
664
+ )
665
+ node ||= REXML::XPath.first(
666
+ doc,
667
+ "/p:Response[@ID=$id]/a:Assertion#{subelt}",
668
+ { "p" => PROTOCOL, "a" => ASSERTION },
669
+ { 'id' => doc.signed_element_id }
670
+ )
671
+ node
672
+ end
673
+
674
+
675
+ end #chiudo classe
676
+
677
+ end
678
+ end