cie-es 0.0.1

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