ruby-saml 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 268a395d4ed90393709bec460cd818d644bd7269
4
- data.tar.gz: 0507a8928e591569b9be618d2dac54b8c83a1847
3
+ metadata.gz: 785edd651b4a713d7a01a4841676034300a5465b
4
+ data.tar.gz: 33e3be90b834541836dad199a3843f0f50dc1d73
5
5
  SHA512:
6
- metadata.gz: 59e8e5300dcd4f521c19216686753cd2ce1a2480383bf6c873601a1031d96ef59241e13076a6b2e89cee61cc43f64c2afc383d6c985a8186cee0a7efe433aabd
7
- data.tar.gz: 00dfd3b12d124c41282b7edce5ae4214f96d696314120776f0cbeadc6c86c35cfac1279b8d3ef0b23ff6559ffec6365681254da2784d86b8fa659a825989ba02
6
+ metadata.gz: 7f0297c376b1b4ae225cd8b93a5541ff06829e89dcb8ee796b1ad1256a401532f1bf96ae257a8e63114f18de72333769d89f83bb4ac188e2659710dd75c9d3d0
7
+ data.tar.gz: ca1e2d0aefbdf122c4c214736c3c3d704184387f5becff71f4f1ebfdb1dc6b4d83a63dd1b267c12decdd69b1a8ada3db5886f4c6b64d46904a254e9dca85f260
data/changelog.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # RubySaml Changelog
2
2
 
3
+ ### 1.4.1 (October 19, 2016)
4
+ * [#357](https://github.com/onelogin/ruby-saml/pull/357) Add EncryptedAttribute support. Improve decrypt method
5
+ * Allow multiple authn_context_decl_ref in settings
6
+ * Allow options[:settings] to be an hash for Settings overrides in IdpMetadataParser#parse
7
+ * Recover issuers method
8
+
3
9
  ### 1.4.0 (October 13, 2016)
4
10
  * Several security improvements:
5
11
  * Conditions element required and unique.
@@ -136,16 +136,19 @@ module OneLogin
136
136
  }
137
137
 
138
138
  if settings.authn_context != nil
139
- authn_contexts = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context]
140
- authn_contexts.each do |authn_context|
139
+ authn_contexts_class_ref = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context]
140
+ authn_contexts_class_ref.each do |authn_context_class_ref|
141
141
  class_ref = requested_context.add_element "saml:AuthnContextClassRef"
142
- class_ref.text = authn_context
142
+ class_ref.text = authn_context_class_ref
143
143
  end
144
144
  end
145
- # add saml:AuthnContextDeclRef element
145
+
146
146
  if settings.authn_context_decl_ref != nil
147
- class_ref = requested_context.add_element "saml:AuthnContextDeclRef"
148
- class_ref.text = settings.authn_context_decl_ref
147
+ authn_contexts_decl_refs = settings.authn_context_decl_ref.is_a?(Array) ? settings.authn_context_decl_ref : [settings.authn_context_decl_ref]
148
+ authn_contexts_decl_refs.each do |authn_context_decl_ref|
149
+ decl_ref = requested_context.add_element "saml:AuthnContextDeclRef"
150
+ decl_ref.text = authn_context_decl_ref
151
+ end
149
152
  end
150
153
  end
151
154
 
@@ -27,7 +27,7 @@ module OneLogin
27
27
  # IdP values
28
28
  #
29
29
  # @param (see IdpMetadataParser#get_idp_metadata)
30
- # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
30
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
31
31
  # @return (see IdpMetadataParser#get_idp_metadata)
32
32
  # @raise (see IdpMetadataParser#get_idp_metadata)
33
33
  def parse_remote(url, validate_cert = true, options = {})
@@ -37,12 +37,17 @@ module OneLogin
37
37
 
38
38
  # Parse the Identity Provider metadata and update the settings with the IdP values
39
39
  # @param idp_metadata [String]
40
- # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
40
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
41
41
  #
42
42
  def parse(idp_metadata, options = {})
43
43
  @document = REXML::Document.new(idp_metadata)
44
44
 
45
- (options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
45
+ settings = options[:settings]
46
+ if settings.nil? || settings.is_a?(Hash)
47
+ settings = OneLogin::RubySaml::Settings.new(settings || {})
48
+ end
49
+
50
+ settings.tap do |settings|
46
51
  settings.idp_entity_id = idp_entity_id
47
52
  settings.name_identifier_format = idp_name_id_format
48
53
  settings.idp_sso_target_url = single_signon_service_url(options)
@@ -132,6 +132,7 @@ module OneLogin
132
132
  # attributes['name']
133
133
  #
134
134
  # @return [Attributes] OneLogin::RubySaml::Attributes enumerable collection.
135
+ # @raise [ValidationError] if there are 2+ Attribute with the same Name
135
136
  #
136
137
  def attributes
137
138
  @attr_statements ||= begin
@@ -140,8 +141,19 @@ module OneLogin
140
141
  stmt_elements = xpath_from_signed_assertion('/a:AttributeStatement')
141
142
  stmt_elements.each do |stmt_element|
142
143
  stmt_element.elements.each do |attr_element|
143
- name = attr_element.attributes["Name"]
144
- values = attr_element.elements.collect{|e|
144
+ if attr_element.name == "EncryptedAttribute"
145
+ node = decrypt_attribute(attr_element.dup)
146
+ else
147
+ node = attr_element
148
+ end
149
+
150
+ name = node.attributes["Name"]
151
+
152
+ if options[:check_duplicated_attributes] && attributes.include?(name)
153
+ raise ValidationError.new("Found an Attribute element with duplicated Name")
154
+ end
155
+
156
+ values = node.elements.collect{|e|
145
157
  if (e.elements.nil? || e.elements.size == 0)
146
158
  # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
147
159
  # otherwise the value is to be regarded as empty.
@@ -236,6 +248,39 @@ module OneLogin
236
248
  @not_on_or_after ||= parse_time(conditions, "NotOnOrAfter")
237
249
  end
238
250
 
251
+ # Gets the Issuers (from Response and Assertion).
252
+ # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
253
+ # @return [Array] Array with the Issuers (REXML::Element)
254
+ #
255
+ def issuers
256
+ @issuers ||= begin
257
+ issuers = []
258
+ issuer_response_nodes = REXML::XPath.match(
259
+ document,
260
+ "/p:Response/a:Issuer",
261
+ { "p" => PROTOCOL, "a" => ASSERTION }
262
+ )
263
+
264
+ unless issuer_response_nodes.size == 1
265
+ error_msg = "Issuer of the Response not found or multiple."
266
+ raise ValidationError.new(error_msg)
267
+ end
268
+
269
+ doc = decrypted_document.nil? ? document : decrypted_document
270
+ issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
271
+ unless issuer_assertion_nodes.size == 1
272
+ error_msg = "Issuer of the Assertion not found or multiple."
273
+ raise ValidationError.new(error_msg)
274
+ end
275
+
276
+ nodes = issuer_response_nodes + issuer_assertion_nodes
277
+ nodes.each do |node|
278
+ issuers << node.text if node.text
279
+ end
280
+ issuers.uniq
281
+ end
282
+ end
283
+
239
284
  # @return [String|nil] The InResponseTo attribute from the SAML Response.
240
285
  #
241
286
  def in_response_to
@@ -300,7 +345,6 @@ module OneLogin
300
345
  :validate_id,
301
346
  :validate_success_status,
302
347
  :validate_num_assertion,
303
- :validate_no_encrypted_attributes,
304
348
  :validate_no_duplicated_attributes,
305
349
  :validate_signed_elements,
306
350
  :validate_structure,
@@ -432,20 +476,6 @@ module OneLogin
432
476
  true
433
477
  end
434
478
 
435
- # Validates that there are not EncryptedAttribute (not supported)
436
- # If fails, the error is added to the errors array
437
- # @return [Boolean] True if there are no EncryptedAttribute elements, otherwise False if soft=True
438
- # @raise [ValidationError] if soft == false and validation fails
439
- #
440
- def validate_no_encrypted_attributes
441
- nodes = xpath_from_signed_assertion("/a:AttributeStatement/a:EncryptedAttribute")
442
- if nodes && nodes.length > 0
443
- return append_error("There is an EncryptedAttribute in the Response and this SP not support them")
444
- end
445
-
446
- true
447
- end
448
-
449
479
  # Validates that there are not duplicated attributes
450
480
  # If fails, the error is added to the errors array
451
481
  # @return [Boolean] True if there are no duplicated attribute elements, otherwise False if soft=True
@@ -453,16 +483,10 @@ module OneLogin
453
483
  #
454
484
  def validate_no_duplicated_attributes
455
485
  if options[:check_duplicated_attributes]
456
- processed_names = []
457
- stmt_elements = xpath_from_signed_assertion('/a:AttributeStatement')
458
- stmt_elements.each do |stmt_element|
459
- stmt_element.elements.each do |attr_element|
460
- name = attr_element.attributes["Name"]
461
- if attributes.include?(name)
462
- return append_error("Found an Attribute element with duplicated Name")
463
- end
464
- processed_names.add(name)
465
- end
486
+ begin
487
+ attributes
488
+ rescue ValidationError => e
489
+ return append_error(e.message)
466
490
  end
467
491
  end
468
492
 
@@ -644,32 +668,13 @@ module OneLogin
644
668
  def validate_issuer
645
669
  return true if settings.idp_entity_id.nil?
646
670
 
647
- issuers = []
648
- issuer_response_nodes = REXML::XPath.match(
649
- document,
650
- "/p:Response/a:Issuer",
651
- { "p" => PROTOCOL, "a" => ASSERTION }
652
- )
653
-
654
- unless issuer_response_nodes.size == 1
655
- error_msg = "Issuer of the Response not found or multiple."
656
- return append_error(error_msg)
657
- end
658
-
659
- doc = decrypted_document.nil? ? document : decrypted_document
660
- issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
661
- unless issuer_assertion_nodes.size == 1
662
- error_msg = "Issuer of the Assertion not found or multiple."
663
- return append_error(error_msg)
671
+ begin
672
+ obtained_issuers = issuers
673
+ rescue ValidationError => e
674
+ return append_error(e.message)
664
675
  end
665
676
 
666
- nodes = issuer_response_nodes + issuer_assertion_nodes
667
- nodes.each do |node|
668
- issuers << node.text if node.text
669
- end
670
- issuers.uniq
671
-
672
- issuers.each do |issuer|
677
+ obtained_issuers.each do |issuer|
673
678
  unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
674
679
  error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
675
680
  return append_error(error_msg)
@@ -928,8 +933,17 @@ module OneLogin
928
933
  decrypt_element(encryptedid_node, /(.*<\/(\w+:)?NameID>)/m)
929
934
  end
930
935
 
936
+ # Decrypts an EncryptedID element
937
+ # @param encryptedid_node [REXML::Element] The EncryptedID element
938
+ # @return [REXML::Document] The decrypted EncrypedtID element
939
+ #
940
+ def decrypt_attribute(encryptedattribute_node)
941
+ decrypt_element(encryptedattribute_node, /(.*<\/(\w+:)?Attribute>)/m)
942
+ end
943
+
931
944
  # Decrypt an element
932
945
  # @param encryptedid_node [REXML::Element] The encrypted element
946
+ # @param rgrex string Regex
933
947
  # @return [REXML::Document] The decrypted element
934
948
  #
935
949
  def decrypt_element(encrypt_node, rgrex)
@@ -937,13 +951,21 @@ module OneLogin
937
951
  raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
938
952
  end
939
953
 
954
+
955
+ if encrypt_node.name == 'EncryptedAttribute'
956
+ node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
957
+ else
958
+ node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
959
+ end
960
+
940
961
  elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)
941
962
  # If we get some problematic noise in the plaintext after decrypting.
942
963
  # This quick regexp parse will grab only the Element and discard the noise.
943
964
  elem_plaintext = elem_plaintext.match(rgrex)[0]
944
- # To avoid namespace errors if saml namespace is not defined at assertion_plaintext
945
- # create a parent node first with the saml namespace defined
946
- elem_plaintext = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' + elem_plaintext + '</node>'
965
+
966
+ # To avoid namespace errors if saml namespace is not defined
967
+ # create a parent node first with the namespace defined
968
+ elem_plaintext = node_header + elem_plaintext + '</node>'
947
969
  doc = REXML::Document.new(elem_plaintext)
948
970
  doc.root[0]
949
971
  end
@@ -111,13 +111,13 @@ module OneLogin
111
111
  symmetric_key = retrieve_symmetric_key(encrypt_data, private_key)
112
112
  cipher_value = REXML::XPath.first(
113
113
  encrypt_data,
114
- "//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue",
114
+ "./xenc:CipherData/xenc:CipherValue",
115
115
  { 'xenc' => XENC }
116
116
  )
117
117
  node = Base64.decode64(cipher_value.text)
118
118
  encrypt_method = REXML::XPath.first(
119
119
  encrypt_data,
120
- "//xenc:EncryptedData/xenc:EncryptionMethod",
120
+ "./xenc:EncryptionMethod",
121
121
  { 'xenc' => XENC }
122
122
  )
123
123
  algorithm = encrypt_method.attributes['Algorithm']
@@ -131,10 +131,12 @@ module OneLogin
131
131
  def self.retrieve_symmetric_key(encrypt_data, private_key)
132
132
  encrypted_key = REXML::XPath.first(
133
133
  encrypt_data,
134
- "//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey or \
135
- //xenc:EncryptedKey[@Id=substring-after(//xenc:EncryptedData/ds:KeyInfo/ds:RetrievalMethod/@URI, '#')]",
136
- { "ds" => DSIG, "xenc" => XENC }
134
+ "./ds:KeyInfo/xenc:EncryptedKey or \
135
+ //xenc:EncryptedKey[@Id=$id]",
136
+ { "ds" => DSIG, "xenc" => XENC },
137
+ { "id" => self.retrieve_symetric_key_reference(encrypt_data) }
137
138
  )
139
+
138
140
  encrypted_symmetric_key_element = REXML::XPath.first(
139
141
  encrypted_key,
140
142
  "./xenc:CipherData/xenc:CipherValue",
@@ -150,6 +152,14 @@ module OneLogin
150
152
  retrieve_plaintext(cipher_text, private_key, algorithm)
151
153
  end
152
154
 
155
+ def self.retrieve_symetric_key_reference(encrypt_data)
156
+ REXML::XPath.first(
157
+ encrypt_data,
158
+ "substring-after(./ds:KeyInfo/ds:RetrievalMethod/@URI, '#')",
159
+ { "ds" => DSIG }
160
+ )
161
+ end
162
+
153
163
  # Obtains the deciphered text
154
164
  # @param cipher_text [String] The ciphered text
155
165
  # @param symmetric_key [String] The symetric key used to encrypt the text
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.4.0'
3
+ VERSION = '1.4.1'
4
4
  end
5
5
  end
@@ -56,6 +56,22 @@ class IdpMetadataParserTest < Minitest::Test
56
56
  assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
57
57
  end
58
58
 
59
+ it "uses settings options as hash for overrides" do
60
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
61
+ idp_metadata = read_response("idp_descriptor.xml")
62
+ settings = idp_metadata_parser.parse(idp_metadata, {
63
+ :settings => {
64
+ :security => {
65
+ :digest_method => XMLSecurity::Document::SHA256,
66
+ :signature_method => XMLSecurity::Document::RSA_SHA256
67
+ }
68
+ }
69
+ })
70
+ assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
71
+ assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method]
72
+ assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
73
+ end
74
+
59
75
  end
60
76
 
61
77
  describe "download and parse IdP descriptor file" do
data/test/request_test.rb CHANGED
@@ -285,5 +285,12 @@ class RequestTest < Minitest::Test
285
285
  auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
286
286
  assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
287
287
  end
288
+
289
+ it "create multiple saml:AuthnContextDeclRef elements correctly " do
290
+ settings.authn_context_decl_ref = ['name/password/uri', 'example/decl/ref']
291
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
292
+ assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>name\/password\/uri<\/saml:AuthnContextDeclRef>/
293
+ assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>example\/decl\/ref<\/saml:AuthnContextDeclRef>/
294
+ end
288
295
  end
289
296
  end
@@ -28,7 +28,7 @@ class RubySamlTest < Minitest::Test
28
28
  let(:response_no_statuscode) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) }
29
29
  let(:response_statuscode_responder) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) }
30
30
  let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) }
31
- let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(read_invalid_response("response_encrypted_attrs.xml.base64")) }
31
+ let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(response_document_encrypted_attrs) }
32
32
  let(:response_no_signed_elements) { OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) }
33
33
  let(:response_multiple_signed) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64")) }
34
34
  let(:response_invalid_audience) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64")) }
@@ -198,17 +198,6 @@ class RubySamlTest < Minitest::Test
198
198
  assert_includes response_valid_signed.errors, error_msg
199
199
  end
200
200
 
201
- it "raise when the assertion contains encrypted attributes" do
202
- settings.idp_cert_fingerprint = signature_fingerprint_1
203
- response_encrypted_attrs.settings = settings
204
- response_encrypted_attrs.soft = false
205
- error_msg = "There is an EncryptedAttribute in the Response and this SP not support them"
206
- assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
207
- response_encrypted_attrs.is_valid?
208
- end
209
- assert_includes response_encrypted_attrs.errors, error_msg
210
- end
211
-
212
201
  it "raise when there is no valid audience" do
213
202
  settings.idp_cert_fingerprint = signature_fingerprint_1
214
203
  settings.issuer = 'invalid'
@@ -365,14 +354,6 @@ class RubySamlTest < Minitest::Test
365
354
  assert_includes response_valid_signed.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id"
366
355
  end
367
356
 
368
- it "return false when the assertion contains encrypted attributes" do
369
- settings.idp_cert_fingerprint = signature_fingerprint_1
370
- response_encrypted_attrs.settings = settings
371
- response_encrypted_attrs.soft = true
372
- response_encrypted_attrs.is_valid?
373
- assert_includes response_encrypted_attrs.errors, "There is an EncryptedAttribute in the Response and this SP not support them"
374
- end
375
-
376
357
  it "return false when there is no valid audience" do
377
358
  settings.idp_cert_fingerprint = signature_fingerprint_1
378
359
  settings.issuer = 'invalid'
@@ -559,20 +540,6 @@ class RubySamlTest < Minitest::Test
559
540
  end
560
541
  end
561
542
 
562
- describe "#validate_no_encrypted_attributes" do
563
- it "return true when the assertion does not contain encrypted attributes" do
564
- response_valid_signed.settings = settings
565
- assert response_valid_signed.send(:validate_no_encrypted_attributes)
566
- assert_empty response_valid_signed.errors
567
- end
568
-
569
- it "return false when the assertion contains encrypted attributes" do
570
- response_encrypted_attrs.settings = settings
571
- assert !response_encrypted_attrs.send(:validate_no_encrypted_attributes)
572
- assert_includes response_encrypted_attrs.errors, "There is an EncryptedAttribute in the Response and this SP not support them"
573
- end
574
- end
575
-
576
543
  describe "#validate_audience" do
577
544
  it "return true when the audience is valid" do
578
545
  response_valid_signed.settings = settings
@@ -953,15 +920,29 @@ class RubySamlTest < Minitest::Test
953
920
  assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname]
954
921
  end
955
922
 
956
- it "not raise errors about nil/empty attributes for EncryptedAttributes" do
957
- response_no_cert_and_encrypted_attrs = OneLogin::RubySaml::Response.new(response_document_no_cert_and_encrypted_attrs)
958
- assert_equal 'Demo', response_no_cert_and_encrypted_attrs.attributes["first_name"]
959
- end
960
-
961
923
  it "not raise on responses without attributes" do
962
924
  assert_equal OneLogin::RubySaml::Attributes.new, response_unsigned.attributes
963
925
  end
964
926
 
927
+ describe "#encrypted attributes" do
928
+ it "raise error when the assertion contains encrypted attributes but no private key to decrypt" do
929
+ settings.private_key = nil
930
+ response_encrypted_attrs.settings = settings
931
+ assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedAttribute found and no SP private key found on the settings to decrypt it") do
932
+ attrs = response_encrypted_attrs.attributes
933
+ end
934
+ end
935
+
936
+ it "extract attributes when the assertion contains encrypted attributes and the private key is provided" do
937
+ settings.certificate = ruby_saml_cert_text
938
+ settings.private_key = ruby_saml_key_text
939
+ response_encrypted_attrs.settings = settings
940
+ attributes = response_encrypted_attrs.attributes
941
+ assert_equal "test", attributes[:uid]
942
+ assert_equal "test@example.com", attributes[:mail]
943
+ end
944
+ end
945
+
965
946
  it "return false when validating a response with duplicate attributes" do
966
947
  response_duplicated_attributes.settings = settings
967
948
  response_duplicated_attributes.options[:check_duplicated_attributes] = true
@@ -0,0 +1 @@
1
+ PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgRGVzdGluYXRpb249Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJRD0icGZ4ODM2M2M2YTctZmQ1MC0yNjA0LTdmZTctZTZhNjgxYWE3ZmMxIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiIElzc3VlSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDoxNFoiIFZlcnNpb249IjIuMCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDgzNjNjNmE3LWZkNTAtMjYwNC03ZmU3LWU2YTY4MWFhN2ZjMSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+cEJ6cTlYdzBMeFBoK0x0M3VuTmdaQ081UzJjPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5wcmZlQWRzRE9mUUZUTEwwYk01WEFOQXh3cmNZZE9RTjF3U1g2eEtoSkRHU2I5V2NPUE1Pa3ZOczdRSUZyaE1nNEZpdXJtc0VnMlE1OTFRYkRVUStoNW5SbFo1NHRVcFh1eDBTOHFYSmxnd0JHWjJUWCtKeDk3TTB4NkJBS3I0cm05K0RuOGk1QW1zdU5QRUVieFhBdm5JRWJVOTNVem5wd3IyNG1OUllpdFU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNHekNDQVlRQ0NRQ05OY1FYb20zMlZEQU5CZ2txaGtpRzl3MEJBUVVGQURCU01Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQk1DU1U0eEZUQVRCZ05WQkFjVERFbHVaR2xoYm1Gd2IyeHBjekVSTUE4R0ExVUVDaE1JVDI1bFRHOW5hVzR4RERBS0JnTlZCQXNUQTBWdVp6QWVGdzB4TkRBME1qTXhPRFF4TURGYUZ3MHhOVEEwTWpNeE9EUXhNREZhTUZJeEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKVGpFVk1CTUdBMVVFQnhNTVNXNWthV0Z1WVhCdmJHbHpNUkV3RHdZRFZRUUtFd2hQYm1WTWIyZHBiakVNTUFvR0ExVUVDeE1EUlc1bk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRG82bStRWnZZUS94TDBFbExndXBLMVFEY1lMNGY1UGNrd3NOZ1M5cFV2VjdmelRxQ0hrOFRoTHhUazQyTVEyTWNKc09lVUpWUDcyOEtoeW1qRkNxeGdQNFZ1d1JrOXJwQWwwK21oeTZNUGR5anlBNkcxNGpyRFdTNjV5c0xjaEs0dC92d3BFRHowU1FsRW9HMWtNemxsU203elpTM1hyZWdBN0RqTmFVWVFxd0lEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFMTTJ2R0NpUS92bSthNnY0MCtWWDJ6ZHFIQTJRLzF2RjFpYlF6SjU0TUpDT1ZXdnMrdlFYZlpGaGRtME9QTTJJckRVN29xdktQcVA2eE9BZUpLNkgweVA3TTRZTDNmYXRTdklZbW1meVhDOWt0M1N2ei9OeXJIelBoVW5KMHllL3NVU1h4bnpReHdjbS85UHdBcXJRYUEzUXBRa0g1N3liRi9Pb3J5UGUrMmg8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+DQogIDwvc2FtbHA6U3RhdHVzPg0KICA8c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBJRD0icGZ4Nzg0MTk5MWMtYzczZi00MDM1LWUyZWUtYzE3MGMwZTFkM2U0IiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBWZXJzaW9uPSIyLjAiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4IiBOb3RPbk9yQWZ0ZXI9IjIwNTEtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9ImhodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHAiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDUxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwNTEtMDYtMTdUMjI6NTQ6MTRaIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICANCiAgICA8c2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS0xXzUiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPmdZYnoxVm5Bcml1bXpqTnoweGNwc0o2QnRkOGtxNzlnb0dqNm5pcWlHY3dMV1NYTTBwaTRQNUd1K2tPejFubWRtSHlnUGlXN3RqalFOTWxBYVFvN1IvNVFURjh6a3F6SXltdGY3a0VpMkhQN29FbHMya1VoOWJ5WkR0akhqdTlueVY4ajlacXlydnZyaFVXNS90RmJHZkJ5UXdYOGhiVGl1TXV5eXcxeTltVT08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4NCiAgIDx4ZW5jOkNpcGhlckRhdGE+DQogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5YWVhaQzNwUDArekFsazZYSGM4aERqQTk4R3MxNWRCaU5SZC9nU3U2MEw1blkzS3NpSkd0c0gzcGlmU2dLN2ZIWisyTzJrYUV3SmszY0djSThYc0VEcnk1YWp0NnRQT0RIRkZGeDFmV1o1SEQ5L1I4RG01U3pQSG9QMmxET2lBZWgwd3pUQjVWdUdrbU5nUmYyNjBkNkpDVndxY0RVc3FrZWJkMWkvTzd6V3VoUjFDRmZZK0l3c1l2bUdXMUlqdUQxcXVJUmdzNnFTSEt1SnhrWlM1NGp0RHhFaUR0VGN0aXhVaytlMHhHRXZUVUxKVFA5eDN5anZEV09FNGJpelZsSFJpVnVmMzRaVWJuV2hURlFiKzBIUT09PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQo8L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXR0cmlidXRlPg0KICAgICAgDQo8c2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS0xXzUiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPndqOVRBbTE4aGNkTGpqdnZ6bzdnbFF0djI3ME5FWS9wR1NpeVhIN0dXNGFWRllWWG5EbEVhSFRadFlmUnFXVXFJMCtENG5FZ1l2dzZzMEIxNzZpcWtNME1QS0ZUdUx5cDBxU2tuVDhYVmVJSmVHQ0xON1NPRFFBTE8ySlNCVk15aFNhM0MrSDN2MExITWhOVzdRMU5VeDA2WEFMYXRnTFBqc3RFaVhqYU41ST08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4NCiAgIDx4ZW5jOkNpcGhlckRhdGE+DQogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT4xSy9SSHFjMHN1ekc5ZUo2ditHUXljTjh5RWtTc2lFdHFFRGZvYnZ6Qkx2UFQrYno4SVRNaDlCL3FJZno2TnhUREdPejQ1WW12WC92aHhoclZiYVcwRUpLemFsQXVRN0ZpMTlOSEl1NkllcW16a29JMGlQMzNsamZjRy91N1lPNmhJeDl5OU1qeWF5MjNFcEh5akIvcVNLaTF1emJzV2JLV21rWU8yUS9TM0NVZ242Mm40b01UQkowcFdKdEY5Y0NzcWxhZ0owZXBpYkl6VksrWDFJRm1aOE85cjF2d040ZzdZNmovNUZGbjNBQnhKT0hhZVNucU95dUU2N1RhSzdmdk9tMHNNU1RBVkV1NWlqWk9Fdk5LRVB0enRHZituNlFiSXF2bmxSTllNST08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICA8L3hlbmM6Q2lwaGVyRGF0YT4NCjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
data/test/test_helper.rb CHANGED
@@ -2,6 +2,7 @@ require 'simplecov'
2
2
 
3
3
  SimpleCov.start do
4
4
  add_filter "test/"
5
+ add_filter "vendor/"
5
6
  add_filter "lib/onelogin/ruby-saml/logging.rb"
6
7
  end
7
8
 
@@ -131,6 +132,10 @@ class Minitest::Test
131
132
  @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64'))
132
133
  end
133
134
 
135
+ def response_document_encrypted_attrs
136
+ @response_document_encrypted_attrs ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_attrs.xml.base64'))
137
+ end
138
+
134
139
  def signature_fingerprint_1
135
140
  @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
136
141
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-saml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - OneLogin LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-13 00:00:00.000000000 Z
11
+ date: 2016-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -249,7 +249,6 @@ files:
249
249
  - test/responses/invalids/no_status_code.xml.base64
250
250
  - test/responses/invalids/no_subjectconfirmation_data.xml.base64
251
251
  - test/responses/invalids/no_subjectconfirmation_method.xml.base64
252
- - test/responses/invalids/response_encrypted_attrs.xml.base64
253
252
  - test/responses/invalids/response_invalid_signed_element.xml.base64
254
253
  - test/responses/invalids/response_with_concealed_signed_assertion.xml
255
254
  - test/responses/invalids/response_with_doubled_signed_assertion.xml
@@ -260,6 +259,7 @@ files:
260
259
  - test/responses/no_signature_ns.xml
261
260
  - test/responses/open_saml_response.xml
262
261
  - test/responses/response_assertion_wrapped.xml.base64
262
+ - test/responses/response_encrypted_attrs.xml.base64
263
263
  - test/responses/response_encrypted_nameid.xml.base64
264
264
  - test/responses/response_eval.xml
265
265
  - test/responses/response_no_cert_and_encrypted_attrs.xml
@@ -390,7 +390,6 @@ test_files:
390
390
  - test/responses/invalids/no_status_code.xml.base64
391
391
  - test/responses/invalids/no_subjectconfirmation_data.xml.base64
392
392
  - test/responses/invalids/no_subjectconfirmation_method.xml.base64
393
- - test/responses/invalids/response_encrypted_attrs.xml.base64
394
393
  - test/responses/invalids/response_invalid_signed_element.xml.base64
395
394
  - test/responses/invalids/response_with_concealed_signed_assertion.xml
396
395
  - test/responses/invalids/response_with_doubled_signed_assertion.xml
@@ -401,6 +400,7 @@ test_files:
401
400
  - test/responses/no_signature_ns.xml
402
401
  - test/responses/open_saml_response.xml
403
402
  - test/responses/response_assertion_wrapped.xml.base64
403
+ - test/responses/response_encrypted_attrs.xml.base64
404
404
  - test/responses/response_encrypted_nameid.xml.base64
405
405
  - test/responses/response_eval.xml
406
406
  - test/responses/response_no_cert_and_encrypted_attrs.xml
@@ -1 +0,0 @@
1
- rVhXk+LKDn6/Vfc/UOwjxTgnamfOdQCTjDEmzssphzY2OOFsfv1pwjBM3D1b92nGakn96ZO6pebnX1XgNwqQpF4UPjaxB7T519N///MzNQI/7sxAGkdhChpQKUw7J+FjM0/CTmSkXtoJjQCknczq6Lwy7uAPaMdIU5Bk0FXzziT+3iZOoiyyIr/ZkECaeaGRnaG4WRZ3ECTNcsd5sKIAAaEdR16YpXf/GVb6ELtxszGQHpuxU9EUypo4TbcJB6XbLIZhbQwlnbblkMC2bdNhOBoqhy+BzaPH5t8UY1pwBW0zJua0UQwHbYvFsTbD4nABI0yTYKFRmuZgEKaZEWaPTRyFruEWGDPHyA5FdjDyudlYvvAI42pCGhuNM5Gds23ydA3Js+MHUBlB7INzYD+Re6WfdtrRvS2kIU9eiLfTGx9lWT6UxEOUbBEcRVEE5RCoY6fe9sd1x6s9sAehE53diUYYhZ5l+N7xTK4CMjeyG7y/jRIvc4MvnGMIhp6ct0FltS2MDH80kfMWr5ucQf6muzdYk9Rop66BXT2e/M2AAxIQWqCxmA0emz9+N53nEOeJEaZOlATp289/hwqEBfCjGNjt9CU4CPDfOfyUtZ/IR4ySt4X1/ifkXYl7dbI0/Bw8cQ6p9+kDpUn6ftIN66WwzV2ZibFk83gGcK98Ftwov3y+K5tbgi8WmKuqo03fVvS8iz3vi8VuS6K6Y4qYWgAjUJ0UoHNQ9GheTEOXSLiWpuBDttpjXQzhPCvrI1JckbN4NqKSgDoG87209riKZ+wQG+4hA1OWmsldR/USKzmYq72lOAbN7liplDk0s0xZyo+k2JpwQZnK+zqIDIp1yP4Bj7JD5fqRbvqBigzJxeMtnDv88FaDwhGobwGuKZSTjMy4fYiny8uBJyUDT8pgIMpHUeQ3mihq4mRiaesoIPClxE+E7f7g7j2ZK1GB1xY9XhJ0RUtLUdtIS02Tu+VwuTh2xwq/l3ls0RUFRdQXZNWb83NhO1kKvDWXun7+LPuuGfRKE69i69idKTx70XeVwRyn/LnMhcaKrCSJH13s0jmPLvPnIw96JVpNJB5VdkqlSlqlSD3jLJu/lSm9QSUe+eHFfjPn/eX8DusAYh3Od92lIijnvYVKUfQVtTdWvXyzFgpT9o/KrFtK5Vl/1C3dqRksFRO3Y3PXVRQ+umCGu81WVKgMZEfhUVnUD7I+MAlJ65444nlSnvCSKHgaDEWTIjpoac/FRkOqMdr1x9s8HmGaZG3GpENNrX2ZTrY6Fy+KJeMc5wexv2fn7ria70lc0XDFGqYqWAyXUwZnR24d7HriodpOyWVezvZcEvM+2grcmlamdr2reVrGyF0irXSaqtOx5Y7IDCnKuCsdUV3zu5GM7ZWj7+sBc3zWiXUCtjwj7SbGYqMdyoHEa7zwPibhEpPAjxW8kEVPQ4qgZdAFibaWa/xoH/o8riFY0cM8UzsOKVIZiupyVaStQls7zz3XDlB1quCDRFow0aEYTQ9TulJ5MBzRfbSeMgq5GROOkenFYBMETr0WuX1G6MURmdRJ/zh1F+EQrQGSLvR1FR61qrQChJuW/CHRDJ7QYm3fp5ja7CFqlNRT0MLd87F4X+g34eUoIPeH5M0xem1ocUfPoCh96Qb3MjGyQeN84r5v++lZu6PnlgXS9KUPIB/cXzoo/zJYXDti9VVHxJC1MtYtFwRG86br/Vq57Z1buwVuswTDkhjHYfASZwinTaIE1QY4gLMBxqAWCjCbAOSfjgV/MhicrO5t9dzcASu7+nuRTiDRA6nRg23GyL7OAPaAnSWe3XbOqh3Igefztp2cstHQpydHWg5nBscDCeQP+H50QtV8SqMARCH43x3UK9LL5u8QXXGKUeh4p51OSbx0ve8LxAo6JjASkDRvDr9xeSrePxvsJlGmhmrCO9kpThzF0bsMcpcMzoDlxR44pdj9ZjANQGbYEMh5JkVeeUC+Qv1SDMgnGb2ECrVt76SanoAKAGYLnOvsHiXRIU8oP0SCfYjkXW743PZO/R/SliWwRb9C+qjz9JuBX4O5mb1j4estrwqvEb8hgs8zNzzdCyCAeWicP78+dygD+dBhKUM/g9AG1akcMBMQcGKkHGBSFMPZLEpgJIYyNEfQtoUDzsZsjgWmczP9klEcfzndHxiFuGAIGaiyT6h8XRR9+FiCA9jTt+8pq2Od9KB4Cv+UUWLfaPzE1QeqP2C5X7mR+ZbnDKbFzDPwZvmy1g2tpI4zYN+U/uBleMcK3LOCtfDq93yKr7c2XPjy2kbJ0zAMNX7AGxjeI3/3CI7GKb5HswLZE1GGFUWGYnEJZyWUkjix2ZjXMfgNf13/HPNblO9w/psn1KtnA6Q4Rbct07q7Gq7eX3vuH7z4vsQJbOj0g8r/JZjT+y0yQNwOtg4Wfwjoq6C87Z+Fdefv1GOeaJygKMe0MYfGSQolLMriaIYhURxYloGiwDmtMMzLNHM2+gTi/bDzFU+iF7sgOQ9Hn8K6U7o8NEZoIIwrZ3z0RsvFqMurG8BIdL7UxWnNFvVq6RKwn4TTrt5itT3vqvkMPqmQsRb3EtTNB4gndmsutgGmbXcbaZzt+la+G3l4JR9W9G6/XiHdfBQFh9V0yuPVJsWcWMDSnDTWC1UTaHXIoEgkWWpaswS5dXuGsPL8LuscaklYCMWqhQ8KQ1k8G2Y5QVIuWO5XR4VICDTz9+54xHhqYsq8bw/6ZW9BHZnpdEbPVGJDOINq11+QqBqO06FFVIODPe4TzjoW0f3WoxZpPLYPGAkoNVrvxtFUJgSVOJYqPxiy64mw2lC5Fjn0KBmZVpE96xvUUYrpxt04k10269VsTXJRMeaccifO5pI/p1pG/5CKcyEpH+Gj7gPVn6X0V0m7anxzRr4pi1+WxMdyeD6KOW1U8pbfPPeXDsNM1qwRP48ERB5KIF/SZk+oBR3lB1tvvS+kBR+Mxdgw5/xKUFr1EeMin6eTpM5VJ2FxUODmcTpZzIqA1DeV4Rb5mJx6ZkiVw32NCj5FLg+BtWjxhx1q84W6PchYTazJUuVCzpyl84LmcCxAwWHG92KX3Y/YMed6yQgTqo2w2eGzuidGPWlaLZ+psk4M4kAegi6JdE8VOqUDZ8GO1yaaR6vhYicvgK/jvMGYz17KHrtxGRWkWGb+ZOdnmueSQcFkmcg7m4OlDXpHYS60JN5AW+vtthLHli0QrUDzZhZ8JDtlvz9ktrMgzIXuFqzmhCiPDGJiMrKyVp286o16VDwA7mobEXt7oo0NP0rY2XJFD9gpMmAdjSB6oDVJ+8twCAtP5xHE+GXZfF8y78vlncK1oX7sjO8a7qdd9bb60hxPP1Igb397ffoH