From 856411062e0b806223279133beea67b85bf310f4 Mon Sep 17 00:00:00 2001 From: Jarrad M Date: Fri, 17 Apr 2020 22:25:39 +1000 Subject: [PATCH 01/26] Fetch attribute value --- lib/onelogin/ruby-saml/attributes.rb | 19 +++++++++++++++++++ test/attributes_test.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 test/attributes_test.rb diff --git a/lib/onelogin/ruby-saml/attributes.rb b/lib/onelogin/ruby-saml/attributes.rb index 31d18c312..42a70c1cd 100644 --- a/lib/onelogin/ruby-saml/attributes.rb +++ b/lib/onelogin/ruby-saml/attributes.rb @@ -113,6 +113,25 @@ def ==(other) end end + # Fetch attribute value using name or regex + # @param name [String|Regexp] The attribute name + # @return [String|Array] Depending on the single value compatibility status this returns: + # - First value if single_value_compatibility = true + # response.attributes['mail'] # => 'user@example.com' + # - All values if single_value_compatibility = false + # response.attributes['mail'] # => ['user@example.com','user@example.net'] + # + def fetch(name) + attributes.each_key do |attribute_key| + if name.is_a?(Regexp) + return self[attribute_key] if name.match?(attribute_key) + elsif canonize_name(name) == canonize_name(attribute_key) + return self[attribute_key] + end + end + nil + end + protected # stringifies all names so both 'email' and :email return the same result diff --git a/test/attributes_test.rb b/test/attributes_test.rb new file mode 100644 index 000000000..c89ae24bd --- /dev/null +++ b/test/attributes_test.rb @@ -0,0 +1,28 @@ +require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) + +require 'onelogin/ruby-saml/attributes' + +class AttributesTest < Minitest::Test + describe 'Attributes' do + let(:attributes) do + OneLogin::RubySaml::Attributes.new({ + 'email' => ['tom@hanks.com'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks'] + }) + end + + it 'fetches string attribute' do + assert_equal('tom@hanks.com', attributes.fetch('email')) + end + + it 'fetches symbol attribute' do + assert_equal('tom@hanks.com', attributes.fetch(:email)) + end + + it 'fetches regexp attribute' do + assert_equal('Tom', attributes.fetch(/givenname/)) + assert_equal('Hanks', attributes.fetch(/surname/)) + end + end +end From 1ae0c82127a875f31188efa2733945f9f49cbf60 Mon Sep 17 00:00:00 2001 From: Jarrad M Date: Tue, 2 Jun 2020 21:45:15 +1000 Subject: [PATCH 02/26] Update README.md --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4aa8dd3d5..d5eae1726 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,9 @@ Imagine this `saml:AttributeStatement` + + usersName + ``` @@ -474,7 +477,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object "another_value"=>["value1", "value2"], "role"=>["role1", "role2", "role3"], "attribute_with_nil_value"=>[nil], - "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}> + "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil] + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}> # Active single_value_compatibility OneLogin::RubySaml::Attributes.single_value_compatibility = true @@ -491,6 +495,9 @@ pp(response.attributes.single(:role)) pp(response.attributes.multi(:role)) # => ["role1", "role2", "role3"] +pp(response.attributes.fetch(:role)) +# => "role1" + pp(response.attributes[:attribute_with_nil_value]) # => nil @@ -506,6 +513,9 @@ pp(response.attributes.single(:not_exists)) pp(response.attributes.multi(:not_exists)) # => nil +pp(response.attributes.fetch(/givenname/)) +# => "usersName" + # Deactive single_value_compatibility OneLogin::RubySaml::Attributes.single_value_compatibility = false @@ -521,6 +531,9 @@ pp(response.attributes.single(:role)) pp(response.attributes.multi(:role)) # => ["role1", "role2", "role3"] +pp(response.attributes.fetch(:role)) +# => ["role1", "role2", "role3"] + pp(response.attributes[:attribute_with_nil_value]) # => [nil] @@ -535,6 +548,9 @@ pp(response.attributes.single(:not_exists)) pp(response.attributes.multi(:not_exists)) # => nil + +pp(response.attributes.fetch(/givenname/)) +# => ["usersName"] ``` The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact'). From 09e9cac115d94439c9c581ac47519d62f0d9d490 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 27 Jun 2020 17:27:44 +0200 Subject: [PATCH 03/26] Add more uni tests --- test/response_test.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/response_test.rb b/test/response_test.rb index f0671c185..a6c7e985d 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -211,6 +211,26 @@ def generate_audience_error(expected, actual) assert !response_wrapped.is_valid? end + it "raise when no signature" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_signed_elements.settings = settings + response_no_signed_elements.soft = false + error_msg = "Found an unexpected number of Signature Element. SAML Response rejected" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_no_signed_elements.is_valid? + end + end + + it "raise when multiple signatures" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_multiple_signed.settings = settings + response_multiple_signed.soft = false + error_msg = "Duplicated ID. SAML Response rejected" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_multiple_signed.is_valid? + end + end + it "validate SAML 2.0 XML structure" do resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test') response_unsigned_mod = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml)) From 55a2389a9065519253e5e7bebef45765355eb498 Mon Sep 17 00:00:00 2001 From: Petri Avikainen Date: Fri, 27 Nov 2020 14:30:26 +0200 Subject: [PATCH 04/26] Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions Support GCM algorithm encryption in assertion decryption. Resolves onelogin/ruby-saml#541 --- lib/onelogin/ruby-saml/utils.rb | 13 ++++++++++++ test/response_test.rb | 21 +++++++++++++++++++ ...8gcm_encrypted_signed_assertion.xml.base64 | 1 + ...2gcm_encrypted_signed_assertion.xml.base64 | 1 + ...6gcm_encrypted_signed_assertion.xml.base64 | 1 + 5 files changed, 37 insertions(+) create mode 100644 test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 create mode 100644 test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 create mode 100644 test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 0e9619d61..e67218b8f 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -253,6 +253,9 @@ def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm) when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt + when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-128-GCM').decrypt + when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-192-GCM').decrypt + when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-256-GCM').decrypt when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key end @@ -263,6 +266,16 @@ def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm) cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1] assertion_plaintext = cipher.update(data) assertion_plaintext << cipher.final + elsif auth_cipher + iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16 + data = cipher_text[iv_len..text_len-1-tag_len] + auth_cipher.padding = 0 + auth_cipher.key = symmetric_key + auth_cipher.iv = cipher_text[0..iv_len-1] + auth_cipher.auth_data = '' + auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1] + assertion_plaintext = auth_cipher.update(data) + assertion_plaintext << auth_cipher.final elsif rsa rsa.private_decrypt(cipher_text) elsif oaep diff --git a/test/response_test.rb b/test/response_test.rb index 4e201cff0..4214c817e 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -1572,6 +1572,27 @@ def generate_audience_error(expected, actual) assert_equal "test", response.attributes[:uid] assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid end + + it "EncryptionMethod AES-128-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes128gcm_encrypted_signed_assertion = read_response('unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes128gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes192gcm_encrypted_signed_assertion = read_response('unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes192gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes256gcm_encrypted_signed_assertion = read_response('unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes256gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end end end diff --git a/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..d42f624c8 --- /dev/null +++ b/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMTI4LWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5FYllOb2p4ZUkvWHNlWGFPS0xpRGpFZHZGVlF5YWo4NnIzZHlkYU9uU2RIbCtDZlZkaTFKSkszSDdkRG9MR3M5IGFZbHdlL21xa0E2ZUl2dTFtNWM3eFQ2c2NUek5sbFlqcWplaUlDVEtaaUdzb2hjTE0xbUt5Zm1mUUpJeXQ4ZzAgSTdSMjE5V0pHSE53bnYwaXU5NzlveW9KRDB4bjczSGk2Vmcxd0NkTWFLUT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8L3hlbmM6RW5jcnlwdGVkS2V5PgogICAgICA8L2RzOktleUluZm8+CiAgICAgIDx4ZW5jOkNpcGhlckRhdGE+CiAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+NFM4SVFhWXZOczlEL3FIb3o0RytMTHhkUVpnWGVURDdESzVLR2Q2dWd4OFhMcThsZTdaRXlGdEdCSHZHSmZjcktSNy9aMm03UC8zZHpCOHFka0E5bUVsT0Q2RlpHQnVST3RteE51WXRDU1B0ZXczUlh3Yk05cGRjby9ZMVJ0NjU2T1ZMM3pYMVovclZ1b0F3QXNvNDlBV0x1SUUwQytiVFRiTnhONGI5TzFtbXFaZWxTTnQ3M2pWd1BGaThmT3A1ZW00SlNVeGFVNTUrc0VTems5blorL21wK0E4aFVzUzdCZUduQzVoMnFrL3grZkkxam42ckRUUTA2djR6K2RGSDgvc2Vtb3YyTVJnNmJwV3dtVmc3b2plSi9WM1RlTTJCRUQ5Z0FLcG1xK3Uwb20zaGd0UEp3K3F1cVVzL0pucUNQN3ZJV1V3RzkrM1BuUEREQkJqM3BRaGdZbkdqbitNVTRrVzZ3Uk1mam5SSWV6WW80K1BuY01BdjhJRmQrNUdMQ0hFMTZxQXI0bG1pT0FIeFoyVFEyU3lFV0V4OVdHUkh6bVBIODM3bDlIeGk1c3U3UTJFdkZ6K2U5Qy9Hd0VDanpqaDdNNWtSTlE5TmlMekZWK08xUml4bmhJeHlGQlZlczgvbjI4WHZmajVVMzZKQzlJZ3FlNlozOWYrYVg0MjV3alFGSTgwcWdDK3pwUFZSTU9sMnhaNThVY1FHcVJreEJra0lmN0N0RDZNNUtKRnQzNXlib2M1blhETUZjQnFMNC9YOWdlcXZPU3FqbXNhd3h3SDc3ZEJGQ0lNVlN5OFFUYkFEQ0ZFMXJHcEUwRTFSTzhsSzNjaFNhelgxd3h4ZTd5ajhTTFIvVm5hTWpzdGlmMDhPR2tQayt5bDFsZDloSTBtYVk1OEQ3VDgva3IvNVcxQVN1bkE3dGFOVEwwa3R5NFNoblp0ZWpScWR0VURkNTRFYUdjazdOZENqMHQ3Tm5sY3VaQU02MkdqVVlqalIvRG5ENklpeFF2alp0eDBYck9GK0xxR2NRUWwySUZPWVhOVWFlbkxoV3FZMXJBNWVWWUlISXNCU1NTYVhCOGQ0Z2svWXhnY3lYRm41eUVqcHFSYzF1QkZJY3hVVjZCWG10TjZiVnpTditqWGZsckJ4SmxsY3hKNVpHbUtsN1FxN2oxZUs4MGtiUkFZc0NjMGNWNnF2ak5aSXk4aklTdEFPemV1OWcrOUYwRWxBSXBsajZtME00RzZDQUFiMHdzcWIvRU92c3V5UTFuWWR2SW52QVQ4L2YwVThvTWRjNmN1dVdGTnRvdi9OZjAvZk1rMk5yTGJDQ2hSalpMOTgwN2NlVGZubHE0VjByNGd0Z3JCQWNFLzlNNE0zUHNNdlQrKzFEVkwvMGMyU0syUVFCeEdzRXhCeVlqdGw5RWREOU5rcFVRVVRrK2FjaGdJWGVTbDM4UjZwaU5RWHlmSVJ2UDJpR2pnOHRVSDhSdTY1OE1FY3RtblBlWXF2LzUxZXFwNVRMVUU4ZzF2alZPd090RHJVc0FMeDNZT3ZCTXI1TkNoTVVsZWVUNHFzSmVNOG5YWkNvVlM1RHppZHpmM3pPWVRxWDNmQkVzVDFFLzNkQzBUNEFldzFjK3ZyT3NZTkQ5bndneUpkcVVNZ3NmK01PVTdYcjIvWXFteFp5ZzcwOXNDUGZURjJRQi9teVVWWTdEem1YQ2gweFVDelVJMS9yczBnNGs3b0RWeWNaQ0pEQ2lzYlU2QWVrNW1pSmFHQ29BRDgyRzZValRIbTV2RjArcDJVT2dCQkY5ME8rRW9aNXREV0FzZHk3KzlZTnZpMEFhU1g4d1h4UElaUWRmcmNucFlybERxS1luVnVWVnhicUlMVTA0UEZqNXF6RVhEYllEWk45YmswYUZpRjNMSlhEZFprTFh5RjA0YVp4NCtJZDA5Qm1CeWRiRlhxWlFsSU1oTU1xYjJ0d3RwM1pmWTZEU2kzeUZBS0R3QVVvR1ovMEJ3bVlyS2pTZFVEcWh0Z3NVVW1VK2xybFZ1bTdhY2RtYktVa2lmc1NHMGlQTDcvQmJrcmV3M2p2TTlwR1FMNzlQTExNT3UrdUo4cVdsYWlPSFFNSHNHVHpQVEhpUGxZVVR2YkxickFCT3FnbzZDblJ6R3BDaXFaT3B1ZjI0WW9hQjFlalR4YU5RcVE5RzZsQlVYMlJQcTEzd3ZuZFFnTGM4QmhjRmhCV3oxcGJ5OTFsRUpMSE9yQjRnamlIZ1d0eFFnVWNITWcxcEY5YXZZNnB0d3d4VEhkR3V3RjlSU0piRVUwUG9RUXJTSVJKem91dHgrdUNMRExaTU81cE8rcmZRQzVyVVZ2Q1ZKZUE0cHpNeHNYRkVQaVprd3V1U1B5SmIvd1lXMzNxSERlb1NNOTY0aXVyb2gxRkhUUW03cGx2VUJYQ3VqYWxWZXNLUUh1bnFvTy9kUVRUcGdUMkpUQXlhazN0ZG1zQVpKcUNqNXlwUTgrVjNHOEU3bEZIY0xVR2p3eVg4VU54eGc3cXhobmVQYXJ4NEhYOVo5MTZVUys5RnpnZzR2djRxeWlqSXdaSHBweWVmbjlyY3lrcTVVUGxyTHhvM0hQazZPaDVaT3FvWEFNRHNOUUVsTjJjdHRSS1ROSjFOcEFwK1o2MlkvVmxBWm1lVzRZTE1ER3Y4THZhN0ZQSllZMldMMSsxekxVbUFJYnRpUnFiVThIaXc3UUcwbW5MQlJlZ3A4Q3dobG9UNEJQSWlCdGFWdWNwOG5WKzhDbENBR21ZczUzak9rQlNsQVdSc2QvNmFTZEpYbFRNT3hFOHdxcXZvL3haWmlKTXNaSyt1MlZnbHpMU3NsSDEwa0FyWTRoTU5LNjlYbndUNi9UQlJUVGtrSVVoQXFpaDVEYnN5bGpLcjNRKzBBbTRsQnVQL1V5VTUvRktFcVBkd0RuOFZGQjdJMWpFUGhWczJrR3grTGM3WHZyK1J4T1FHUWVqblBZVHI5azZ4NFI2OHEzeUM5WHE2NlZIUlBSR0tJMFVhSFI0ZGZmQys4OFFkWnZBMFVkMStuaE5vSzhPazJHYXVyZUo3OXB2QytSbG5QaWFRUHpiS0treWtxREg1ZExpdWR4U0lZY3ZXY2NvcmpJa0h1ZHJORmpWM1AzQXlKY1VucGMwNHRwc09QWldZckFtTjZ3ZWdKMnYzay9tWVJiS2tVOVdua0Y5MWQzTzJMR2htUzBZMUR6Ni9nTlR1aUUvbk1pMDhFYk4vSU5DMkMxeGNXQkZrUVRUWGRVM1lvUnFxOGRqdXFSUG1SRUhMVWRoU1NKaTFiVTE0ZDVYcWNUMFhLR2NZekdtR29QMkFGT3VWTTZiWlI5VXNvcXFlQnFWZVVHN282eWI5eGxxNXV6VkhHWnFSU0NUUUNKRjgrNHJNSkw5TEYwUFZEL3p4U1Jpc2xnWk9EbHdEcjFxL2ZOMituMzI3S2w3ZDF1SncrOS9FcExIaFNzWm90ZzhsemNGenpwMUZ3SzJjNEdlZ2MvYjBZb3l1aGd1ekRLYy9sRU1QdmNuVFR5NUhGZ2VWL2JkMmxkOXJBRWRGeTdwR3lEamlERU1nV3BLdmRneHF3cWhzaTVJWjN1QXRBK0VxVTk5bmZBeTVMQXArZytaWHZuY0N0OStmb01ldEFrWmxsZGNyUm9iZytPejZuaWtzcTBLd3V5ajR4L253ZUdlTnl1dnpWSnBEZklya25xanB1VFVVVzZ0L2p5dXU1U1l2VTA0NFFoMHVzdjVpQWJyR202SklOZStYNWQrMjNHNkgrbGswUjZEYVU3ZXREUXc4ckxaMHg4dGZVV2xHengrRnhmempISUpxRStadm5YTm1HY1F0Umordm4vN054bVJHNlQ1dlIxQ0oyUEdoOHhBRk8rRWlaekRQcXBTbWp6NWIxRGhLNWIrdlVwcWVZMUxMU3lFU2NxaUR2eWxaWjB6VFFoZmdwNW1xZHI1OXo5NnJ2QjNjeUVuM0FrUzVqbGcyN0tOZE1rV2pNZnh0T1ZUVXRzZGdrdzA0cm12dzE4SWxrQUluZGwyMkltMTVNUFU0UWJEU0Zra2VoR1FPclQ5a1pUTjJuVjlIaXRBSHVRMUxGdjhEa1Q3RVhrZ00xYUVoUkcxRmdPb3FsYUdTbytlUnNYZkRQc2hVYkxRMHRZTk5BeUJjckFhUGxCVkhucGIvODQzNmJuVlpyRURGdENERjhuNjJTNkdQMnNjckh1bW5wQWd5TUsvMFFJSi9NYzdrK2tZN3dUb0hzSkJqUWFEeU9nZU51c0NZck9ZVFdENU1MTVlvcXRGb2xCM1lERkhIS1JjRk5WOUp0M3NsZjRMeVBPZ2FYV011bENpRlk4WmVOQy8xNzlpRUtFbkY4SUJMZ2lOYnRxY2ZPbzdBQU9yaVpiOXhVQWhROTZHRUlXeEprMHp3U1pzQ1BYS0Y5Mk9HODlkNUpKbWk3OHVkUnZic2hUbmExaFlQd2owVEZVRUMyNWF3YVZYaWQxK2xFdVBRZ0toYU1VTENITW5VcUhVeStiRWM5R1ZSMHNMN2ZGZGpiQk4xRlRDNFA1TmV4V1NQT2VLd3g2OWNQbjd2KzVKeWI1TzFmMURyNGtZeFpzODBITlFsT0hOb0Jhb2c4REFuT09naEVEQ2RjenRqelAyd0VoRHhjVXN2dy9QaDhUOUJrcmFDa1h1dEhPU2dDTFBIeDJrdExxWW1HTDFvUmdWNHg4b29EUTNySmZ6UXlZRHpWL2ZWQXpQMEVQeU54TmpxK05VREN2bXlCbjNQOWw0cUs2eVNHTURYRm5COWZYdDVlcENKTW1nZWhZaXpETTJMTS9OVnRvM0NCYm85akUvUTJJcm9ma2w4V2VVQjZMVmtEYTRzQWIzcUI2czdzQ3hUSURHbWUwWEw4Q3FXNG42QUdRTzRyMmV0ajJwTDVpLzlWbGV4eFZuOFVHZkcvamZ3WjltNUlIYmdacTF0SVpBL2F4ZUgxQnQ0T1hRR2QvQmM3Yjd3bGJZY3V5ZUlqVVhuZ0hSdzNjZVhwZGhJY2tvbnpVRTBzMEJhaFNPbWovN1ZzMWx6M3d5Ulp2YS9BNDZ4azQzNTdBVVJRVGcxK3hmVkd3dHJzVHhRSGZyWlhydFdYLzBMY1N3SDF1aFM0cXNHaHZuZ3hhWGtFc05NZUlUa1RjVmxpRWxxb3NLKy9SaWFmNE4vRXB5RjdDVFBUeHJTQm1lSzFRTmYwejhycFBXTzYycG5nWjFkcjJjekh3T0dFQkM3U29CTHIwbDBVYmU4TnRxTC9YZnlwUHQ3MVFoVG42bkRjMEtnUlBpZC9zei9vU21Cd3NrVTlxTjhmZ0RMSGRuMVJLWU9tMkdyc3MyTDN6QmZGeUM1alV4STBtMG5pZG53anZudy9tR1FFa2xOSzhpaTRZT2ZYdDlYL3ZNSlg0MHZvTFV3UmZhOVovcWZydGN1dlZVWTdScmc2aFRnc2hpVzNDTk54dExBWUd1YkMxTUZCSnd0REVzL2FhbjN3elRQOEtnYVdNb2YrZ2JkdTVpZUdkcDlheHhrWmx3SXQ2dlRvUm9BTGlYRkY4NVlkRHgwVWlQS2lmcXRBNFV6SGxoblZIdG1Dek43QTBKMENSS1FSUG9yVWpGNGMvVmFOYWh4UjJmUGxoQ3NsVUJqZFhQQ1FCQjA0VkhKdFpHeFoxSXIwaG90cnF3YVh1MGtKS0dKQUVWZnRhV1F3NkRhaUg0eXlOMzVybVFCWENlZ215UEZRekdpYXFSR3J2TkIxekVWby9VTzlmQ1FPbDdVWVlMZldBYXBIalJNUnlWZG1VWGhFR09OdzhIMjYyZVFxTVBIVEY3ckxXVVNmeGtoQWR0ZmQ3N001VzU5bjRnTHRLWXV5Wm42cTBoOXA5Ny94QXA1c3hzZlJvejc2RWlmYVJQUGxtTW5yK1laSlhGRmc0Tkt1TzE2YzFBalFreHlObWJNVEJ3T2taVWtSUndZUWMyUFpvVFBGejJ2Y1hMVUVIOXA0ajIreFh0c3Q1cXZPZ1cxRlZISHMyZ1A2WHg1bTBFQjJyZys3R0tBUlM4VW5aVDg3ZXNVcjdreXVlV3JBODJmck5nWUpkNWFhYnVjUFVTOU1LUlp5SnA3bEJZRUthNU5UMDNqejBuNi9MWkZzM3JXcWlNUWhhZmNSNW5wQzBjREtvblVVY0tGWnVsOU9ONmc5TGN4QWJHRkJCS1J2L01VMnRrT3QvRHRlNTMranF1QklYWG02d0hTYzVNLzBodVl3U0F0S2lIUDE4OGd3SDllSnNEUEJ2di9KSzE2MjU2UWNFWGk0V0RDMFZqbW1rZjB1eDN2U1RlUkFtd0g4RzhXSnVuVWJpaGRKckpFWTBUSG9temVOWUFSb2tTaUYva3MwajlySS9Za0oxbWQyNXZadGR3QXh0L0tqcVptUjYxb2NDYVdYNlRPSHpFUzMyNVdHRzJvdE5aZitXTTNaUThsNEhJanh6SVRXOCtnWEg5T2FrRXprYXBqcGtyakp3MGZNdVArMkxVTW1XVmlxL0dRTDVRbVFPaWkvTDNvQW5kZEdBRUZBNGRySUZldktLdE1maFZrVVQvRXRIQnFGV2dkbVV2V0tyOXBTa1ZPaVJFaXdid2xBaGFlMFFNaDdKKzJPTUdCQ0RUUzNMcGRLUEYxR3ZwZ1pXSDN3OHpsSU9Ock1mbSs4MjQvTnNFYVZxUU80VjFWSDFhNE5FaVI1SEtvNENZTVBtM0xuNGpuMU0reFpXVGE5cHU3VW1qRi9mbVFLSFh4UDl0UmZrR1h6S0VZajloSWtKb2xDK0tsOWw2aWtWcHNyQytRbmh4QUFXZlY1YWRvNzBWb2xSUEtsQjlmOGVFcTNPMjB1REQ2ZTZyNloySVhBb0grbXhwWVArZkhpUzVIcHpyTUE1YXlkL2YzV3krNloxWTE4elVsSjI2QVBQcEdPTzZlWUsyeWVzaVdZS20yRERvcUd2S2haaFhBM0o1NjdCTFg3eXprb25KVWRqKzJkd2VkMGY0ZDVvaE1RVU8ra3BBT04yTXRtMHV3NnM0U044NzZzWGtyNzdwLzQyVWc3SGh2UzdjTUVIVnpOTGtWdDg4V2RyVmpVTkxuRG5wREVSTTdWVEZJTUNkUVNjaVJUSm5ZK1RUbHJROVdVb2NNVG1Pck5NLzdqMWlzUFB1RFlaSmxXRjdFTld3Y3pwMWVFVXZTTXNFVytOMDQ5STJmUWI2bk1wdFRyRHh2T2o1cmFNSXdaMXYzYlIxWXd6MHJXcjZwTkZMd0E9PC94ZW5jOkNpcGhlclZhbHVlPgogICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgIDwveGVuYzpFbmNyeXB0ZWREYXRhPgogIDwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+Cjwvc2FtbHA6UmVzcG9uc2U+ diff --git a/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..5fb9a7033 --- /dev/null +++ b/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMTkyLWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+CiAgICAgICAgICAgIDx4ZW5jOkNpcGhlclZhbHVlPjNiSXZ2bFA4b0w2TS9zdWY3Zk04R2hVYW4xYVNoTERxOFQwaWRPYUF5S0ZZLysvYkpYYk9iSHhKendBVWxvY1MgVTRrL1crc0tsL3J5cCtEaW11L0ZPQi82dm5sNnErNlJaMjZjVXVUNWFQQ0xWS3A4Q3RpL2R6SlNyT3lYQkR0dyBCVDB5dklZUGxnWEpJL3lzUnJjR2tENXBVeU9EazJWOXZtNjl5M3ZrNzE0PTwveGVuYzpDaXBoZXJWYWx1ZT4KICAgICAgICAgIDwveGVuYzpDaXBoZXJEYXRhPgogICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+CiAgICAgIDwvZHM6S2V5SW5mbz4KICAgICAgPHhlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5lcjVEazdjWkx4bVJKR3JLTWlpZm1SdjNZTXRlUGZ4MWQrcEM4Wmw1NFBMeUlkSmMxY3JZcU9Mc0hNa1lSVTVnMkZVM0VQcTBGZ0YwNjlYNUFHckQ2WUxhZW5rS1h5RlNrTkpoQStVcjBGMTlwUTBZNU1SSFJoblVLRUZvK0ErT0lwV21qc3I0cEJrOGxycno3TmlUL1UzNGoxM3BiSWJ0eHN6OW5ha054YXNLYXN4bEozb0VxZ2R0REF6RmhtRUVHek9WeGVBM3dCVUVQcWFQRGpZT1dLVEZ3YlJSYlBoZHB1eE1EU1lob2lUWGluRDR3QUd0QXdjeGo5QXhLU3MxYm8zRGFRaWJKeVQ3M3UwM1E2amQ1ZWxRQnRLeklwcUxqWDc0RXdDOXN2eU1uenJ0cEFYK0tBM1lLdnM4K1hmZG43R2s1emw4em43UXY2aFFBdUs3eW5TdlQxRSsxcFBEU2pxdWMwUEhCNm0vS1ptbERjZldhQllXYVE1UWtMMkVTS3JzKzFqb2RkYnpoYXBRU2dvaUJoeGZiT1BtM3lrd2dMSUkyZ0ExUmNBSSs2amtoSXNZM0xmNjZqM0pUeTRSTWM3VVdwRGI5NVZVaFZYN2pBa05nYWZVNzZGUW15bGpGY2RaT2VEL0pSc0tGWWljREdlMERsMUczRyt4OE5tTnhMVndXS1V1SUJSM0RXeElwZk1kdHRkaUlpbGRNa3I2K1U1Q0cybjVURW9VNGNuVWJ0dkI2VTRBdTNVQVNuU3YyRCt5V0JIemNycGp5SXNxSkdvOVhCaUhrUlN5bEphS3B3M1hIc0RJelJWMHZSVUs3K0c4djNaQVdNYkpqUDFhcStYckNYT29EYk5RbzF1bGJxQXQxRUFhT29OTVRLRVBsNmFKMllvSGpSUUcramJoeHhlRXFpK2Raa29RcWErQ3JTbFpSVXI1V1lkWE5aU1g0ak8yRHdTY0lNMnhWSnA0SDU5eWVIaDluVVg5S2hjWVlxUmtIQVNzY3lZT0M4Y1RWbXdSWGpmOHdHb1JsWVNtY2dpYWhHbkRZN2l5eFlVSUR5UUZrYUhWdlgwWktpKzd6YlcrMjNSYUZObUJvVGdCYU5RSmprN0RsVVpTM0lJQ2tsQ0dIWTVWSlFmN3lGRmdITXBJVTBFUVRtOXIwN2NGekRaR25idjU5dmR0Uyt5aElvcDFUSFRsOU9SOVRINDhxQ2RpV29NSWtjV0lENFVEZEZoNXF3OEFneXNHdnVuTElMK0RHbHgwb1RsUDUvOU9zUlBlRXovTzhmTDN6OTJTUmQ3UHRVRFBMbnZ0TjVMc3hOSGJlOEphazY0dG1TZXNhYnFvS05qZEpIcno1bVlqODVrQlR3ZlRzdmxMOVpmS3FYM3oyQUlkakdoUjB1bHFyQ2IzYU5EZkRYRzE2eFNiSjFRRUwranMwZFlRMEtGc0VWYkNKRjVyNDNkcldRME04RjRkTnYrRW9VQXVSQlVGbXFjYjR0eXlhajRZTDVGNEdsV2dzaERrNkpWa3lnYmZPZHliVWRKSUlFTDUzWGRIdEc2M2JDZHV6SHR2YVFLTENaN0Z1Zjdud1pPRnRzZ1dXQUcrTVpsQmdoNzYrWHZYbFI2clZ5RmN3LzI1R0hsaFZFa1JvTVUxUzQ2bmpQRlBXS1cwamdjYWlGTkZ3QXBnc2VSWEFwanRGTjg3R0pNSS8zMjNGQ1dkaVkwR1FXS292Uk5jSU9LNjJYQkQxTmlhVDJKM0lQdFN6UkZRL09zYUlOU1J2OXVURitZRGFKUlA5L0hOUkdKMWVtbXIvaTg3WlFYU0k0amtsY0NTZmx3MVc3cG14RGhuWTlXa3NpdzErUEFJajZSTVgyejZVS2w2RVFTcThmalN6REV2dUoxYWZCOHhEZ2VkaDlGSVcvZTh4VXBXN2dCR2hReE5taW1qNU9mQmROSzYyTzhDT3RJMThDcU9jU0k4UENhaGZOSEdMS0hlYm8yMlYrcXMrQUk4SHV5S202RnN1S2V5aVZPOVo1UlVMR2YzUW5sY0ZvU2ZuWFJ2TXc4ZzVPL3ZRTGdvcUtzaXlJTkE0V3JKc1V1OUFaMjJOcUFhTWxac0h3Sk5IZk1Fa2RYQ2JIWVFKWFdhRVNJMm9wbDZSNi91RE5yUklWREk3VHVCNjE2bHh2VlM0LzBkZ2IwSThPaU9PUHhuQ2Q2blhmT3lTRVFBeVBka3pMaTVseWVFOExYWkhjV3BPdkRrY3FDWXRscmp0UmtkQVoxTEhiRDNsOEQxU3gwTlB2YStLeERURk00N2N0WXhOZVVRQm9EZGlOcVdEYStHOUZaZ1FpNmtVbUhqZDl5d0V6OWFELzllRFVUVkhBQmNoZWJ1azdaaUEzcG1xb1laa09hdzVGSW5JUlJYYVlMZkVENWFGN05sMWJJY3djMW43amJQOEplU2dFNEI0ZzZ2dklZdzE4K0NiR3hVcUV1bDVBTWhKdEMrR1RBWjk2aEJlQjNiQjI1Q1lwMXBvZ01HMldXMjRiNkFjVHdUQWhwOXhtYzYvOTdvbHZiTFJtMGdtTVkrNG84NnI3cWZLY0F4dUZYQUYyaHYzWExaN2cwZUdhWkJRbUdqNTE1d3pjZ1JMTGFmQ1JNRVo1STdVNUdTMFRjc2I3aG9zMzNzMnZxMnd5QnZWUmEyNHhwbzFsTW1DRlJqbS8ya3VqUlZtZ1MxZ0F1N2JQMEtValdRK1ZPZHU2OE8xRXo5TDlOSFB4NjQ1RnFpU0dLTnI5QnNwd08xMzFiNUp6OTNaQkRJNnVjdnpvNGU5anhQWFNZbmRsa1RWR0diUmRoOHE1bm1lQWxEZTNMYjVqM3RrZkVSQ1BPcklnT3M2SW11bXllRG5wNU16bStjVTVXNDRSM2tKSlB1cWRYdFZPZWVkTVFza0d5ZkxnckxvcTU3Ukh4OWNsVnlSQnpXaExkOEpYYW83WHhMbWNwVmFSSEJSYXNEaVpSc0NLSFcyV2NkcXJqWlFZOGFWaS9oL1NBVy9vUTUzRWtDSTJnZFhXSkhlN1JWVHZwWnZCcVJMRlNvbFhBQzZNbXFISG9xUmFBMEtRRGJUMm8zYzZjUVlGNk43VkV0NnZZNnhlMXNtR2JpU1pXcXFpSk1lbTM1UWtxUzNwN2hQTWVNRUJCb1VCcEEzeGZScFg1MVM0N0NmOTl5N2l5UWtjcWV4NjgrTUZabXlCVzRsOFZrWiswRXlRRXlydEtKSDAzcFZyNUQxa29QMExyMWZ2cUIyNVo4TzFBQ1duWDladkFYbUhocG1YTUlPNWU0ci9QYUNlakVyZGRqYTlKb1BsMEtrVDZhOFk0R1hMZXJKUURwTHdDTzQzSCtIa0hGSERyVFUyRHMreCtteUpkSkFOSkJKZGtraytMTERvVlVpVk1UUi9RS0tCUnVRR2Q3QU5IY1Y2OGJwUnp6eGZOa2czWHgvN0o0NEY0ZHlLQ0RnazFYdUR0U080Q2dsNDZNR2NCcUFzeURpRTlyZG5ZRmsxTFRac0dTWStQbUloT3huVzBUK1dxRVRBVE9IVk5FRTFBb1Q4UkJVSnRRcXdWTXR3cUx4MVB4bWZnWHVKU1RIUHhFZCttRkpTWFMvdFlZUmJUTGFDdmJxL3lwTkxSSlZVcEY2K2ZUck5GdUM5M2RZKzkvOEVLaklyL1NMR0twai96bXFIVjdXYmgwUlVXMG5tbzdZM2RIdXdlaDJrdlNiQjA4MXkxOEFmaXM3dUhnTFdsdW1UdTV1M3crNEZidmMzYUpDQVFMdjFCV3VuZy9iQVZ3U1g5dGtuWmpGSUgrNnRMY1QrSGVjVW9DanVBcXlRaWdpdFZSQzFrZEl3SFVPZThxVzd2UXBNbVdOV2k0aUQ4R2U4RmpMbXdNNWpvZWtra0xWNnphMjlKc3U2WHc1aTNjNWh0ZjR6eGp0VWFOekNXTDY3cFBjVEFETlhxL2tCMTRCRmFSYlVWRVNVa3BCcHJZTXFOZnBkeTZMcXl4Nm5TMHZJeGhyWlREdkpCRENQekE0NHJxeGFNTkZUSklOYXRoMUhkMm56ZDZwdjdYQ1FtRzAxbW9FMHIxNWFmRVkvcE9CcG9KSHlKZzNOZWpBMVlPSVRJNy8yWU11VG5MRUw4MXE3MHpicDM3L25Vd3ZwcytBUUZRdmdEclVINkxWQnk2alhWeFhnOFJIV2pDeDVTa2JHNjFDc2lqREROZXR6djBUNUNuallDc0pPZnAzR0xheFRkQzBEZ2NKYTlqdHpzUnJhSml6UklPbTZDdXJ0ZU45b3dKejBMVjZWMVJQTGdiQjlIL2liVVVLeXZjc1p1aTA1alBLMWlFVlZKSGJBNURLVFZkY1c5Q3Rzdjh4WTIrRXN1RmFnY3QzVFhDRmVtR0QwU1VsZ29uOFJZYmZPMWdRK3JjblBjMHZ3eUcyQk1GL0VwaEVlZG9iaHNVWmwyeis4QW9kc1dxRnJiOTh0ZEFTMnNXQ2liM2NLMmFZcGo1VWlRbnR3Nzd6THJaNVhzcHdjOTIzajJJdzVVejNyVm94UldhM0dUN2ZPdG5uKzlnOFpwUXRJdUhrUG1SS3BWNWVZczI3cUZLT09xZ29iN3VVNHlQRVh0c0UyK2dFRkdKeEpuZVUwbUpsOUtaZW05WUR5bTJSSkIxREMzc2J4M2szTWxXN1F2Wmx3bGJQek1GQ1lUYm84ejBWQTEwVUtMS3RsT0x4NlhPRCtRclFVU2hJMEVUMkhIb0MyME92aFYxaUlIclREL2w2ZkErT1YrcGRPVENCNVNQMWpldTlHbC91T3FNUXlOVTFJOGJ2ZjhJT3k5bTNSYW9DWnpYT0NpZ3kvKzM2aFlpMFFiQWtzWWtuWk94bThMQ2pZaU5HQVEraXpmMzBaR2p6ZWlEd2kvS3hwTzQybU9uZml5ZXMwS08zeDRDWWF0VllvYklYUkkrcDN1Z3FCNGxya1NKUnN1blNvRjc0eVhSYmF2Y1dPT2cyOTEySzNMaE1JUU4zMFhmditYTFRXVzkva2VSVlUwdE1lWU42MWM2eW53OGpFd04yQzVNYzk4NC9WcCsyZVpYY0lMd3FkNXJQQ3p3NStSdnFJU25JOFlBb05FL1Bvalh6RGpmbTlSZVVYanduQi9hcHIzOGZqMDFNRVM4NGhUY1p6U2swQWFWdXUxOThjQ1RlYzVxTEp3SjhiK3kxM3dQNFErWXlXWXFZMXpnZHdBQlB0QXo2ZTJ4L085Tis4M05pa1J2RmZDUVlScjZaVG9iMDgxVmxISCtKUmdOWHBFNTVCakhsVzZ0M2lQUXhUNkVYZDd6MlBBcG5hdGpOd0wzS0R5bGpMY2RKL0xuTmU5NWw1UjN1M3FmUmlHd1FXb1J2MittQUxGVFUrOHB6azJHUDNCRm9pU3RLK1I0SFoySjJSUEhDQWVWaUw5ZE1FZ0lyczArMllHL0EybVNUNWkvSkFWamxMSHVzU2N3dVhDS3ZoNC90ck9HeGJPUDVNb21DTTdIc1I3ZjRhLzVveXJyMkJ2VHhJbXFSeERLR1diSU9PR2xaMytUaTFQd1pjVUVYNC9oQTRaUEVSOWFza3BEYlpwOHEzWUN5NE1wbjdzN1JrZUxsTmFUN25JZHJkTnRuWnF0aUxCZHJaaXhCZWlUNXdCb056K3FkenBTdXJtTUxEMXdhQnlGb0I2Qmx3K1hEMWxkUWtlVVRZaFBkM0M1aVlER3ozbjR4M1Y2NmkzQ1p0UWlhQkc2ajA1dVZTbU0wYVhESHMxZXY0QlNlekZUdi80dkl2Rzl0WVB3aDM2TGl2Vjk0YVJGQmRqVHRpcjR0L3NUTVNMUHNBMFVuMVduNVZuUnl5MGN5U0d5QTNISHFicnYzSTMwSjhpSGV3bmNtek5qQUh6MlExeDZ0YVhyL1c4VlZLNWtuVUtwTnVuSzRPMktGaDZUVk54WVdUcUlUQXRZYXowN1ZydnRoK3p1bmYzZkx2RXJUWXY2WE1oUXo5eGcvd1FxMzZHZ1AxSE10Q2ZSY2VjU0xTZkswYStTRWI0WmtuRFpiQ3h6ci9lUDJxcmcxcHUxOExHYXpTZUJ3OUZKU2M0VTc5S3RpaFlTN2lyV010ZVh4MzFNMWtRNnpXUlFUQ0pNeWVUeEczdnZWVm96MFNXYXdnQWRnQVBkY2c4TWU1dlNleWN6WkxINW1QM0E3eW03RCtYV3JPT05qVktMTVZKU0hxdkhnbXZMeEdOSHB4aDRIVFJNdTgyanVSamhtRStHY0lHSE4yeEE5c0F2N0szQWo0OWhsZm5RL2owQzBkL3NqLzQ1SVRKUU9oSXlhUEY4dDVxOWd4QVhNeWJpWnlCOUpMUmtDdzV5RnpEaWxEWnE0bTl1Rk1aUTNyOG96MHp3WWVSYjcwZFlQNEU4WEZyZXdqSlNTd3NhR2FDckFqNzZrcE1haU16SXdLK2tEU3NEbCtjU250bTNhbGNtSVRSU01tTmFjdDFiOThTeTVPSU5sTDVhVEZ5WjhKYU1GS0FFQTdoWUF1V3I0cDYySUhUNTFQZlZyRDN4cTc0VHpCWWZsdzJoOStyOXZNUlFKbHdYaWNqK2dUbW1BeHpid1RUdkkrMTMvMTFITFowNzNjNTI3dW5iYXBpcW1NSlBjbHlsandSQzhaZS9ST2hZbDA0S01MT3dLNjY5SHdYNUdCQld0SXVPVTlYcXBPNUtxQlV5WXJqZ1NsTnJKMXlER1RDN3kzNEtiVHgybzRlbGhSYkR2S3c1L0VQK3d1d3QxSERsbkpLUzdpTEppU0hONmJDS0EwVU1GU2tIRXB5c3lpcDNwUWJuL3hqdjNZR2VwV0J3WTNNZUZ3MHROQUdkeUtlcHhKbDFNMkRZQ0R4QkRGazJwWG4xODVDWEN0SjRxY3NXREhmLzY4RngxZVhTZk9GUlk1MTYzVXJHanZvQ1Z3R2pGU1hNbENCMU5ySUlaaTRHRW5SVVoybDFVV3Jtb3FrM0p5KzdoSWFHRFJzZ1poa3BLcG5yL1d6Y1lsSnpKcHF5b0RQeWhzTUhoSFlCcXhKLzNjdGY2cUJ0SjgxTUkyZDBRZktVbHNNanVWMGJxNUpBcTBKYWdKTVdxbldwK1h1YjdPTUR0bkVhdjNEblZxNUNKSzNDRHVUTnNKRnBsSzFRSHVubjVlTXJ2STF0dEdkaVd5SlkrRGxCT2puRnQ3RnRtY0lGcDhSQkc1RG9VYktkNjU3Z3VPTFYvT3cwMGN5a0ZPSDdWQnNWV3ZuZzUzOGtaVmVHTkExL3k4WlhRUHIyZXhYRDZUTT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgIDwveGVuYzpDaXBoZXJEYXRhPgogICAgPC94ZW5jOkVuY3J5cHRlZERhdGE+CiAgPC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4KPC9zYW1scDpSZXNwb25zZT4= diff --git a/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..cfcb85dc6 --- /dev/null +++ b/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMjU2LWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5nMmhNbVVoTVU0MFdFemtZTW1RK1Q4WE1PSlhiMXNWMGFXS015cTVxLzhUdkFWNVFDZ3BHRmxIUVBMQTZEVEJPIGpRRGFINkZrK0c0cWNmcVZ0VFI5Zy90WC9iZWNyMWduaVRzQkI1Zmkwd1JPRmVzejVpUUZ0bHdyMVpkdkdrd3kgaTVYNmRzR1B5VVYyKzBDL1Y2dU1HZXhXNUJSVDBWNDREeEpHdVpkY3JxUT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8L3hlbmM6RW5jcnlwdGVkS2V5PgogICAgICA8L2RzOktleUluZm8+CiAgICAgIDx4ZW5jOkNpcGhlckRhdGE+CiAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+cDJWc0RDSWFjYzB5Ry83ME02aHhpSGp2dzUveFFqb0JDT1ovZm1xcEQ5WnlwejlrVldMU0UzM3lvUkJOc2g0bGZqSHFpYVdSRU9oSDhuQVpMV21QY3gvWjNVTUxQWU1mdWZrWDFCcjFSTGp0VitITDEvRjBwa3pNTnc1VEZjY1dHcVpMbUVaRCszS0FpQ2NMY0lVTnJDeTBBaDVVMWlzTjdxd2llT3ZsRlU2eFZsUGZqN3JuY1o0S29qWDFDQkplaEFpS1ZIVmtWb25LeUxEOW9ocDl0NHZHbzBJbWlFZTRLTEF5ZTI3R3FEL2grQnJZMGdneXJ2UjdFYVJ0Ym5MYzVvdFRBQ1BpMmVua1ZJYS90SjlrUitCcEhNTnI0Qk91eEhKYXRCMS8zSUhCWmZvcTAwc3NNdVRGdXFETk9RT0tUYkxsYmpGb1Vsa3ZscmRrRVlZRzFtcERUVmppRFRhTW40UFdYc1hFb1pueks1R3ZFWHE4Z3ROTXZIRk9SbVBmVmx0dm9ZL1JpMlpYTU1yYThEdVFNOEUxQTIzY3ZvQzZTbmE3dCtYdmRJdmtETE5TNlE5dEY3cFJWL0VDVVp1QWVQdXRNVCtCSEFBZmY5YkY2ekYzOTZBUmE4UHdlZVdZMHdEM1B3dVRuaVlNeFZYV1hIWHl0SytqLytqT3FvcXF0aTZOSHdxaXRuWWNNcHJQT2hNbytDWXRVNS9VUE1SbkN5UStoYXhaaEFUS2pNcTY2WmhMZ1NibEtmVjljK1NYbUhCaHRiNy9pemdRcnN0SGNjZFdOSUIrRDdVQ3JtQjJhMXpna2t1U1FlaEJEZ1lyeUJ0cHkrdk1rN042SDFDYXpkNXFqb2lSL0JFOHlrSGVRVFhNWU1mWklUZUpWYUY5QjlqYzBMeEpLN3ZpSXNsSDZzeUxsdGpOeGhyeTlDcHR6UFM4ZGJOYU5ReTRMVDVqeEdFeWtSTEg5eFhZTUFQOGFIWldtV2FxUlMwczRleGdKVjExRUlmU01TZmNPTjZzb2MzNVM3MGlTWjJVZE9TV2dNM0xiRlQvdmltUHU3S3o2TER5VTlobGVZT0xTbE0zUjl3dm83eDRtdFhjeWthR2VkeGU2TjFENEV3Wk1TaGR2bEMydVpUK2VJWEkzM0RHRVRVcVhyb0dBWkwweUl3QVpEemRDZE00S0VQTndIZVQwTFFVVUZTTHRibE96VFh6Zkw0VHZ6cHRqWGF0d1JvVWI0cmdxSkE2VUwxeWRvY1dKcUFyMkFsVS9hV2hTc2lWVm5ONS8yMmRpVHRnQTVxWTMrTGl3ZWJZMWl2MlB5YTUvTTNhcFQ2VkZtRWpUdEo4aGIyTDUyWksyZlhqVThvUHM5YmQzQmtQR25zWWt1cWxBYUJaaW90MWFVcWVnRVlLdTFVSmtSVXdVK21Qcys0TDNWemFqTW1LazNFMk42U2xOL0NvdlFpS0FnOCtIUWlYVzVmczZQd0M1KzRQYnZuZjFDcDN6ZjVKQmRzT21JYjZxeVlkek5YOUFxaFJORnlZOWlXZWlZTHB0eXdEUnluVitXcm5VSlNUcis1Y3Y1UUZDcTRMbm5FaUhFek0vMW9CWjZPTXh4WE5qelFMQUJDdDZuMzV5TEFCVllFSlhKcHpVWHU5QTduaW1GSmExaWxmS20rL1lMRitXaFJJeWpEbW5oVHM3NnBkY0UwWFR1eTFjSGJ6TGlrcWpaRGtGbytFUkdBM1JnYmFPZmc5VmZiZGNIRUdSTkxSRENIbU1ZS211UjVCbWUwT0RFazJjV3dpQlBHWm5YNXcwSjNqRDJWV2hOU1k3UzVJREwxNzRvVkR3T2J2RXo3VWE2Q2wvc0RKaHlWRWdjaDQ2VCtRbkNXd0hMaEo0TkJsWkEwUWFrSzBPNWxTeit6cE50aE9zWTIxZG80cHZQd1NPNXJuMGt1M0VZNkhscjJGVWgxaXdxdEU0cDF5c0I0RVVTcXNGRjlQenQxN0xHNHFyZ2FqU2lBVkVRUDZiUmNLNFp4WTJab1RDQTBHU3JYVEZLVGUwY0tzRDFoQVlyN08rN0VzVENqRFB5YTg0bVlOQUplS0hFNnFwVitCQk1aUW9rYlJFU3VpU3pyVEp6Si9ubDRMZnNxdFBteWhVdmcxL2ZudXBESlVETEE1VDIyMjE3c1FYZmtmeHdoVXJzSTk3dEpvZ1JDRkU4dSs3VlRrbUVCY0RLRUh6WWJNaEdwanA1U1dyS24rUlZHaWxHaUdOSFpkcnJOZzlZT0xWajk5RkdhQ2I5V1F3TVZsMXoxS2l5ZGJFS3pnKzI4QmdZRlJsc0ZZb1NRQUJhejEzbHhGQU9JVm1hVnNLYXRWdDYwQ25TOEdGeUw2MkdDN1kzY1FzazlybndHL2xqQlYxNS9LMU9WY1YyWnFybDV3clkwQ21pNW01eWR0UC9IVTZPaEdBSlZ0RjJoYzVRYTI0dWJVZ2JsemFyeDhmbG5XVEUyb1NLOXo0Z2xSTjQ2K2hzaDBiUXV3ekdLcXZON3dhWFlXRFhGRG5aSGRZOHlDTlhTdWVDUy82eFoxakFWNG10aDlyRWpJZUNDNFNXKzVrN0ZWUmFYanpEOUJOSjRwZHRDNzRSZGdFWmhlVFlOSThPZnU0SS9ER2tob2dJVHVnOU13RmkxdmFBTm9vSmJEcHNnSGJYL2lIYjNkZklMQXVxM2pidm5xRWswdHg0a1BicVI2SWtKSVdVU2pSQmh3SlJRLzRoRDFTbGNXNXBObDVsRW1BRFRZZFhVUk0xeGdlM2Y1czB2d0FBRzUrRDc0a09FWVJOQmNNaVRWaGttbFkzTVZQUWdVOVJZMFBUV1ppa0IwUDA0UGVkRDhkMnRTQXVmd01zTWZoRVpheTVwUXpTaUg0Zjd4T1lMSmt2aFJkZFRiM1BYdUJXdkQxcDZSeWxMMVFmN0h0RVh0Z2pvTzdLUVo3SUVJNFgxOW85cDdDU01JYU5qR040VUNlNUczYkJkQWhIbXFndnFBSXplOTh2cjRuTWZUZ01FUFY4RWpTRnpVWExNM0RyKzZScEpMckFnRUZ0bGhEREpoNVRMZUtncGFybFN4Q0RMazhJWEdPQ2ptZ0o5TDIzMWphV0MzK1NzY2cvWURkTWFLVUtaSG9nUDBWUlB3MitCeERiRXovaW1kMTdEaXprMU5FRFg5M2FDSGNWMWE1b3M3cnR5ampsVGRUTmhueVFhb2RYK2tyQ3NUdnFnaUR5ZGN4bjR6NW1VdlUwQUpvWW1DU0N1QnhnSTJlMmV0UmREMTBUU2lUNklGWFlEWmVYM0N4SHMxN1NmYXhjSUV6TGdtMGdsRVQ0VUxUZEdDblVlY2xoVHhxeTd0OGF0ZytGcUdDQlNEZWpaNTloODN5M0lnRE0rNjFwVTRFZzQ1R2JqK0RveFY5OGlSSzVPelpBNCtSTTl3eFlZaDBmQ2h6bitKczBQakFrd0ZGZ0VqOWpLaDhNWWJwd2hGeDM0cnUycGNUSGt2dmlVdjYrWDJIZmQzQisyeTVCUzZ5Z2UxWWVDYkZpamlJTkhQQS9kNHlzSlVLZnZTM0duSEYyaG1wWTF2UEZLRmVlRUh3VFJtVkdPUExKN0hWMGZKQVNVemtQWmVxWDIwcnZDdmdnOEdRbm4yTDM3dXdCd0xvYlM1T2xSamUxOEdLQStTYlBSZ2U3Q25rUjZoVzhDSEl6SStwVlJOTkNzT2VTV3lUUTY0Q1p6b3lTeHJxdFNVcnMzUWVhMWZCOG9JRURyWWNIU05ja3dHMmt4WUJLdmRqbzhxa0ZaVlNZWlhjV0IvRnhSSkxObU96OXN0TFg0ZnJiMnJGTzMwd0w5R2FaOHFFeHl2clJzb0lTeWExei91ZE8zR0wxU3k2a2V6WGxpK1ZNRGRRV2pyYkRXVnczT0dmSXFKUWRjTlhnN2pYa1JmeVFLaGYxODlxTWdtL3dxcmpUUUR6aGNhREsvN0tvSnZVOFJ6blFHb2FIMmJTOURmREdObDBqc2ZBQmVDVUpPZTBtVlA3aUZNa0xmeG16L3RyQ0ZMMElRbHFBdXlDM3h1Wjl2VWtKRDE1S1REcW5kQlQ3TEdMMWxpbGpCU3BQYXZLQzArTUVuaHMzUTJRM0lKcDdoaE1wdGcxd3NQY3o2VUNOMTBTb1pqV202cXJGRGk1SVkxcXlGMFlKT29tU2J0VGo4SXZVZEJtQ1ArRmxUQzR5eldOY3FQQm13MHJlbDdqd2xHZFNhN1Z0dVpwazhFK0t3TFBTRE1hUUR4S0w4Tm5XZXNuMFF3dVEreHYvaWU0ck5qVlovSXoyWHNrbG1Tc1VTclg4Q1h1V05YNmxTWFlpVzdnMFIwTXg4ZnFZS1RsUWZ2M2Y1U3VpenZsUGx0UzhCMlVnUlRPUE1PTEc2Z3h6dElpMXdBSFAxT2hkVmtlZGRmNTdiVlN4SDZvQlh0V3gzZEpFT2FVQ0lXOXNCQ2FhN3Z0RGFWS2pUUUQyWWlZc1BIMlFuTWFjZlZqR3hIZi9XTWZHRWlJODRwaTQrMU5jQVE5eTFFc2hZRll4YVB1S3h2L0dNRUpqbGlmejFYL3I1czNxbC9PenpGTXNkdDFlcTZGVWx1Wmt1NklFNGVkUldrZXRDc1F5RHRxVS9qNzNiZWthSGhpaGwrcjVISzB1QkVDZDdIVGE1M3VOaXVRNEpKVzd2U3ZFK1JmbStiREdrcjFmZHBOQkVjSHZaL1VxWnhwV3VmUDBPZ3B6SGdmVS9mSS9GQ3haTXFpemFjZnhzUTJoZi81TCtnMXB5eVJnMXNWamxFTFByOHpObHFTWldhOE9FK2JmbFVjWnAyamFsVkZKYzlpR3gvQ0dJVnlacEJ2Y3dqc29DakpFekVnS1I5Y1REbE92aVlFNzBEQmVJVFJub1ZDeUlaS2p3YiszUHk3WTRkQ2gwSmR4c2Fmc1Rrc1UrRFh2cHhoNHpjVXpKTUM1L1hZa1hXWjU3SGZnWllFSkI5WnNjU3pVYTZWY2lxVmI4SElSR2RINlNhSjluK0pJZWVQV3ExMFZCVGdvT2Jma2RtYUcwT1VITDVTNjN1Q2NyK2dBOWxXRHQva1p0V2gvWmNyK0NhMkl1cGtGZHFYTWpWNXNka3NJdndDTHZKenlYVjFZeExaU3Z4OFFtN0JVVmxFZ0doSUxpNEphbkRVLzlTeXRURytCMElRTUdKWTM2T2d1QllqN21lL2UxZXkyeHlSbW8xU1U5bnpCU1FHV1lLTWhrZlYzajhlclZxaFA2bFJaT0FxM21uaHNBRUpCZ1FnQVRmQ0VsMk1GRFRlSTdTbFZ6aTIyRXZSc2Zvc2ttVFlhWHNoZG5uY0N3alp1WUlieDR3TWtwL3RtRVBzMCtTWndJNyt1NDE5aVpWelVmUkV0Y3JMVXEwUHFKWVd0RCtHOEJnZy95NTZMWUtHM1Q0QXNxUHM5SjZhWDltd04zaHRnSDdEWEpkb0R2dzhxMzluWjdBNm4wV1ZxYkZDMU5oWDdsczZsT0Z1NmsyRm5YSkxXUEwwbmxGeGZPb1JNTi9zN3h4Qk1KMzlJck9DL2htdktmY3RZRHRSeGpHZS9nZ3hibWoxMmJDbGozSjk5c0VzZktWb0VkMUNHbDRDSVl3dWcxQkN1SG9DMngwbXd4VCthUGljV2oxVUV2YUxwM1kzLzFTdVlxSjRxN0xWMlY3N213Q0szNEhpbDZPUXFhdUVjdVdBS0lybEs4MGJ3aG5ibEMrSTZPajh2ZGlMem9lTS9UOFhNWDVVU3lmbDNNeFcxV3EwbVdaN0luRGU1TElnRmZueHNzRXdZcCtYcldPVzdIemZpWnVWSmpTNDBaWnZTd1EzTGlEVTNVQmVVWjdrb0l0Ulp3ZEN4M00wN1c4TXFVT3dqa3JCZUZSVEhRSWZ5dUQ4UkFlVEpZQ2NaUFJtQzRYcDVyU2tOa1dlUmpoVEFZajAvc3NQSUdPMkZsN3FkVVRkYUxtMFJCZ0pMMGRjWnVjZk94djhJZHVtSndETUNaSlllMjlMY2NZbENYUTBFNklhNmpqWDFueFVjaGVNMkloTVNhZEY5WnNNL0pTSzlPNTFHVk1PYU82WUlrb2lrMTBiUjNtbkVhUlRiMldUMDlaRzdCSVZFTG1oYkdGTkppcWZVNEdxdklkakJzSEJVNjhqdkd1dDBSVTI1ZHdHSVk4azFQM0RPdEZjbTZqcTkvcGZZK21BWFVISTlBeGxJUE9YVmpySTZQUkJTY01HVVZSNUJ1L0VJOHNJSlMxNm5mRDkrQWdnbCtPUXAwUXVuQk5jcWNoTUorN2NlWDAzb0l6cVVnRHZvZCtqOWJYN2hnZ1UxL1VySW9Dc3JZYUphcWZjaEJXUFd0QkgzUS9VMVhFOWhrbUdreWxKamJqRU11MzBxQ3ZMbm1Fc2pEMlpJTllSWmVnaENPRCtvR0IzTXdaWXZuQmJtckNZRVZEbGV5S29oQ3cxSmtrZHgxU21JdVVIdC9tMXlXME8zb0RJdzdPdWpDWGM3RTU3U0MrckErcjB0eGFJNDV4aTh3YWkyTTN0VVl2NStObGJDWllhNkNoeVhSRVZkWXJnSnY0cVY0YzZ4UXlUSGpmY2xCZENsazVxWGtOUDlHcmMzQnI5V2N0WFVpZVBiM3A2OTVPS1NpMDUyZ1p2WFMrclFXakljekk0eG56NXphcTBuTmNMRktOWWR2MHF5cG05YlA2YVp1dEczRWxEQU9aYWdGS3lnMEFIdHdaeXpma09UVTUxQkp5ZTdneU9WR2hQaXcvSHhya21iRDMwQnFjendtKzJXOFgvU1V6Zk01NWhVcjVuNHJDNVQxOXAwY3RpQjAzMzJPU0UrbUdWbGh2c3hvQjY2bG8xVG5zZGFqZ2tKakJnRzk4Vnd2VE5UN0JOUDhCSWlNaS93enBiQUh5MndoQWtBZWFRS25HUGdNL096OWppVmI4Tm9ia2J3bm8vTEd4Y1o3L2w3Nno0OElHMWxlZXJBL28rYVR2TmN0ZldNbXowaW4raGhYOE9FVnZkdzMvbFk5UUJXNWxqWWgwVWZpdmEvWG1oQVo4TjJlWGtCcHNXZGFSc1dLdys2TE12bnJnVWdmaWZyN1V4TDJKTS9UclBqRktPR3IzK0NiR2RXMkF0WVFPQ1FFcHRObFJKOStmVklQVFZVVGZZZzZwb2V1NjlMMndYbEZmeGpxeFlQRVBPZjlTU2tNZ050NWN2NzEvSi9rbU9LMGZGWWlDVElzWUhKYWIwNXFqNXFSaTJKUndmTWZqTWNRTVQrOHN0R0U9PC94ZW5jOkNpcGhlclZhbHVlPgogICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgIDwveGVuYzpFbmNyeXB0ZWREYXRhPgogIDwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+Cjwvc2FtbHA6UmVzcG9uc2U+ From 62ac2fe7e9c797baecaa21c8f4b5a62f25a3ae6a Mon Sep 17 00:00:00 2001 From: Jacob Klapwijk Date: Tue, 8 Dec 2020 10:27:17 +0100 Subject: [PATCH 05/26] Parse & return SLO ResponseLocation in IDPMetadataParser & Settings Co-authored-by: Sofia Canclini --- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 16 ++++++++++++ lib/onelogin/ruby-saml/settings.rb | 1 + test/idp_metadata_parser_test.rb | 21 +++++++++++++++ .../idp_different_slo_response_location.xml | 26 +++++++++++++++++++ .../idp_without_slo_response_location.xml | 26 +++++++++++++++++++ test/test_helper.rb | 8 ++++++ 6 files changed, 98 insertions(+) create mode 100644 test/metadata/idp_different_slo_response_location.xml create mode 100644 test/metadata/idp_without_slo_response_location.xml diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index 3cb45e21a..951ff3c14 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -212,6 +212,7 @@ def to_hash(options = {}) :name_identifier_format => idp_name_id_format, :idp_sso_target_url => single_signon_service_url(options), :idp_slo_target_url => single_logout_service_url(options), + :idp_slo_response_service_url => single_logout_response_service_url(options), :idp_attribute_names => attribute_names, :idp_cert => nil, :idp_cert_fingerprint => nil, @@ -304,6 +305,21 @@ def single_logout_service_url(options = {}) return node.value if node end + # @param options [Hash] + # @return [String|nil] SingleLogoutService response url if exists + # + def single_logout_response_service_url(options = {}) + binding = single_logout_service_binding(options[:slo_binding]) + return if binding.nil? + + node = REXML::XPath.first( + @idpsso_descriptor, + "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation", + SamlMetadata::NAMESPACE + ) + return node.value if node + end + # @return [String|nil] Unformatted Certificate if exists # def certificates diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb index c5a40caf0..668334991 100644 --- a/lib/onelogin/ruby-saml/settings.rb +++ b/lib/onelogin/ruby-saml/settings.rb @@ -32,6 +32,7 @@ def initialize(overrides = {}, keep_security_attributes = false) # IdP Data attr_accessor :idp_entity_id attr_accessor :idp_sso_target_url + attr_accessor :idp_slo_response_service_url attr_accessor :idp_slo_target_url attr_accessor :idp_cert attr_accessor :idp_cert_fingerprint diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index 436a32ae2..4f34630de 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -591,4 +591,25 @@ def initialize; end assert_nil @settings.idp_slo_target_url end end + describe "metadata with different singlelogout response location" do + it "should return the responselocation if it exists" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + settings = idp_metadata_parser.parse(idp_different_slo_response_location) + + + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/logout/return", settings.idp_slo_response_service_url + end + + it "should set the responselocation to nil if it doesnt exist" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + settings = idp_metadata_parser.parse(idp_without_slo_response_location) + + + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_target_url + assert_nil settings.idp_slo_response_service_url + end + end end diff --git a/test/metadata/idp_different_slo_response_location.xml b/test/metadata/idp_different_slo_response_location.xml new file mode 100644 index 000000000..aa53cfc5b --- /dev/null +++ b/test/metadata/idp_different_slo_response_location.xml @@ -0,0 +1,26 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_without_slo_response_location.xml b/test/metadata/idp_without_slo_response_location.xml new file mode 100644 index 000000000..d6af0a1c1 --- /dev/null +++ b/test/metadata/idp_without_slo_response_location.xml @@ -0,0 +1,26 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/test_helper.rb b/test/test_helper.rb index ae5dd35c4..1d42b837d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -200,6 +200,14 @@ def idp_metadata_different_sign_and_encrypt_cert @idp_metadata_different_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_different_sign_and_encrypt_cert.xml')) end + def idp_different_slo_response_location + @idp_different_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_different_slo_response_location.xml')) + end + + def idp_without_slo_response_location + @idp_without_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_without_slo_response_location.xml')) + end + def logout_request_document unless @logout_request_document xml = read_logout_request("slo_request.xml") From 0cb184316a6ef5cd39d7053c6a5388416ee6ec06 Mon Sep 17 00:00:00 2001 From: Jacob Klapwijk Date: Tue, 8 Dec 2020 10:54:28 +0100 Subject: [PATCH 06/26] Use Singlelogout response url in LogoutResponse if set Co-authored-by: Sofia Canclini --- lib/onelogin/ruby-saml/slo_logoutresponse.rb | 9 ++++++--- test/slo_logoutresponse_test.rb | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/onelogin/ruby-saml/slo_logoutresponse.rb b/lib/onelogin/ruby-saml/slo_logoutresponse.rb index beecb1016..827e1d0b4 100644 --- a/lib/onelogin/ruby-saml/slo_logoutresponse.rb +++ b/lib/onelogin/ruby-saml/slo_logoutresponse.rb @@ -32,14 +32,15 @@ def initialize def create(settings, request_id = nil, logout_message = nil, params = {}) params = create_params(settings, request_id, logout_message, params) params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?' + url = settings.idp_slo_response_service_url || settings.idp_slo_target_url saml_response = CGI.escape(params.delete("SAMLResponse")) response_params = "#{params_prefix}SAMLResponse=#{saml_response}" params.each_pair do |key, value| response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}" end - raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty? - @logout_url = settings.idp_slo_target_url + response_params + raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if url.nil? or url.empty? + @logout_url = url + response_params end # Creates the Get parameters for the logout response. @@ -109,12 +110,14 @@ def create_xml_document(settings, request_id = nil, logout_message = nil) response_doc = XMLSecurity::Document.new response_doc.uuid = uuid + destination = settings.idp_slo_response_service_url || settings.idp_slo_target_url + root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } root.attributes['ID'] = uuid root.attributes['IssueInstant'] = time root.attributes['Version'] = '2.0' root.attributes['InResponseTo'] = request_id unless request_id.nil? - root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty? + root.attributes['Destination'] = destination unless destination.nil? or destination.empty? if settings.sp_entity_id != nil issuer = root.add_element "saml:Issuer" diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index 189037cf6..176fce951 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -65,6 +65,16 @@ class SloLogoutresponseTest < Minitest::Test assert_match /Custom Logout Message<\/samlp:StatusMessage>/, inflated end + it "uses the response location when set" do + settings.idp_slo_response_service_url = "http://unauth.com/logout/return" + + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id) + assert_match /^http:\/\/unauth\.com\/logout\/return\?SAMLResponse=/, unauth_url + + inflated = decode_saml_response_payload(unauth_url) + assert_match /Destination='http:\/\/unauth.com\/logout\/return'/, inflated + end + describe "when the settings indicate to sign (embedded) logout response" do before do From 2b93476bbf8ac312fa919d3dde61232327be3e98 Mon Sep 17 00:00:00 2001 From: tknzk Date: Tue, 29 Dec 2020 16:58:23 +0900 Subject: [PATCH 07/26] add runtime dependency --- ruby-saml.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index 997a42b03..5b3a866ec 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -44,6 +44,7 @@ Gem::Specification.new do |s| else s.add_runtime_dependency('nokogiri', '>= 1.10.5') end + s.add_runtime_dependency('rexml') s.add_development_dependency('coveralls') s.add_development_dependency('minitest', '~> 5.5') From 59a22a0ab2c57ae0bbb25a0f4acd662f49f6f838 Mon Sep 17 00:00:00 2001 From: tknzk Date: Tue, 29 Dec 2020 17:01:13 +0900 Subject: [PATCH 08/26] test against ruby 3.0.0 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 12a22ec81..a46d2b16e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ rvm: - 2.5.8 - 2.6.6 - 2.7.2 + - 3.0.0 - ree - jruby-1.7.27 - jruby-9.1.17.0 @@ -45,5 +46,7 @@ matrix: gemfile: gemfiles/nokogiri-1.5.gemfile - rvm: 2.7.2 gemfile: gemfiles/nokogiri-1.5.gemfile + - rvm: 3.0.0 + gemfile: gemfiles/nokogiri-1.5.gemfile env: - JRUBY_OPTS="--debug" From 32a0aabe81ff97d4d3a633df5a376ec418878790 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 23 Jan 2021 10:44:52 +0100 Subject: [PATCH 09/26] Fix Travis --- ruby-saml.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index 5b3a866ec..aba6eb998 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -39,12 +39,13 @@ Gem::Specification.new do |s| s.add_runtime_dependency('nokogiri', '<= 1.5.11') elsif RUBY_VERSION < '2.1' s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1') + s.add_runtime_dependency('json', '< 2.2.0') elsif RUBY_VERSION < '2.3' s.add_runtime_dependency('nokogiri', '>= 1.9.1', '<= 1.10.0') else s.add_runtime_dependency('nokogiri', '>= 1.10.5') + s.add_runtime_dependency('rexml') end - s.add_runtime_dependency('rexml') s.add_development_dependency('coveralls') s.add_development_dependency('minitest', '~> 5.5') From 7808e037ec943f104594d1f8fed8554531c38d0c Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 23 Jan 2021 12:20:18 +0100 Subject: [PATCH 10/26] Remove 1.8.7 and ree fom Travis because it does not support them anymore. Adapt json for jruby as well to prevent unexpected tPOW error --- .travis.yml | 8 ++------ ruby-saml.gemspec | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a46d2b16e..2d92c4ebb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: ruby rvm: - - 1.8.7 - 1.9.3 - 2.0.0 - 2.1.10 @@ -11,7 +10,6 @@ rvm: - 2.6.6 - 2.7.2 - 3.0.0 - - ree - jruby-1.7.27 - jruby-9.1.17.0 - jruby-9.2.13.0 @@ -22,10 +20,6 @@ before_install: - gem update bundler matrix: exclude: - - rvm: 1.8.7 - gemfile: Gemfile - - rvm: ree - gemfile: Gemfile - rvm: jruby-1.7.27 gemfile: gemfiles/nokogiri-1.5.gemfile - rvm: jruby-9.1.17.0 @@ -34,6 +28,8 @@ matrix: gemfile: gemfiles/nokogiri-1.5.gemfile - rvm: 2.1.5 gemfile: gemfiles/nokogiri-1.5.gemfile + - rvm: 2.1.10 + gemfile: gemfiles/nokogiri-1.5.gemfile - rvm: 2.2.10 gemfile: gemfiles/nokogiri-1.5.gemfile - rvm: 2.3.8 diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index aba6eb998..608ce68a9 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |s| if JRUBY_VERSION < '9.2.0.0' s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5') s.add_runtime_dependency('jruby-openssl', '>= 0.9.8') + s.add_runtime_dependency('json', '< 2.3.0') else s.add_runtime_dependency('nokogiri', '>= 1.8.2') end @@ -39,7 +40,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('nokogiri', '<= 1.5.11') elsif RUBY_VERSION < '2.1' s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1') - s.add_runtime_dependency('json', '< 2.2.0') + s.add_runtime_dependency('json', '< 2.3.0') elsif RUBY_VERSION < '2.3' s.add_runtime_dependency('nokogiri', '>= 1.9.1', '<= 1.10.0') else From 6f8046ed9e8077beb882e1342c6a7283fc007286 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 23 Jan 2021 13:26:46 +0100 Subject: [PATCH 11/26] Adding idp_sso_service_url and idp_slo_service_url settings --- README.md | 12 ++-- lib/onelogin/ruby-saml/authrequest.rb | 8 +-- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 4 +- lib/onelogin/ruby-saml/settings.rb | 34 ++++++++++- test/idp_metadata_parser_test.rb | 56 +++++++++---------- .../logoutresponse_fixtures.rb | 4 +- test/request_test.rb | 12 ++-- test/settings_test.rb | 24 +++++++- test/xml_security_test.rb | 4 +- 9 files changed, 103 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 87e6314c9..d7dfda600 100644 --- a/README.md +++ b/README.md @@ -261,8 +261,8 @@ def saml_settings settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" settings.sp_entity_id = "http://#{request.host}/saml/metadata" settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}" - settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}" - settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}" + settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}" + settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}" settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1" settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" @@ -327,7 +327,7 @@ class SamlController < ApplicationController settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" settings.sp_entity_id = "http://#{request.host}/saml/metadata" - settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}" + settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}" settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" @@ -400,8 +400,8 @@ end The following attributes are set: * idp_entity_id * name_identifier_format - * idp_sso_target_url - * idp_slo_target_url + * idp_sso_service_url + * idp_slo_service_url * idp_attribute_names * idp_cert * idp_cert_fingerprint @@ -623,7 +623,7 @@ def sp_logout_request # LogoutRequest accepts plain browser requests w/o paramters settings = saml_settings - if settings.idp_slo_target_url.nil? + if settings.idp_slo_service_url.nil? logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'" delete_session else diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb index a4fbf0ca4..621526a1a 100644 --- a/lib/onelogin/ruby-saml/authrequest.rb +++ b/lib/onelogin/ruby-saml/authrequest.rb @@ -31,14 +31,14 @@ def initialize # def create(settings, params = {}) params = create_params(settings, params) - params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?' + params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?' saml_request = CGI.escape(params.delete("SAMLRequest")) request_params = "#{params_prefix}SAMLRequest=#{saml_request}" params.each_pair do |key, value| request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}" end - raise SettingError.new "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty? - @login_url = settings.idp_sso_target_url + request_params + raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty? + @login_url = settings.idp_sso_service_url + request_params end # Creates the Get parameters for the request. @@ -108,7 +108,7 @@ def create_xml_document(settings) root.attributes['ID'] = uuid root.attributes['IssueInstant'] = time root.attributes['Version'] = "2.0" - root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty? + root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty? root.attributes['IsPassive'] = settings.passive unless settings.passive.nil? root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil? root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil? diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index 3cb45e21a..deedbd9da 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -210,8 +210,8 @@ def to_hash(options = {}) { :idp_entity_id => @entity_id, :name_identifier_format => idp_name_id_format, - :idp_sso_target_url => single_signon_service_url(options), - :idp_slo_target_url => single_logout_service_url(options), + :idp_sso_service_url => single_signon_service_url(options), + :idp_slo_service_url => single_logout_service_url(options), :idp_attribute_names => attribute_names, :idp_cert => nil, :idp_cert_fingerprint => nil, diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb index c5a40caf0..dcd0fda57 100644 --- a/lib/onelogin/ruby-saml/settings.rb +++ b/lib/onelogin/ruby-saml/settings.rb @@ -31,8 +31,8 @@ def initialize(overrides = {}, keep_security_attributes = false) # IdP Data attr_accessor :idp_entity_id - attr_accessor :idp_sso_target_url - attr_accessor :idp_slo_target_url + attr_accessor :idp_sso_service_url + attr_accessor :idp_slo_service_url attr_accessor :idp_cert attr_accessor :idp_cert_fingerprint attr_accessor :idp_cert_fingerprint_algorithm @@ -69,6 +69,36 @@ def initialize(overrides = {}, keep_security_attributes = false) attr_accessor :assertion_consumer_logout_service_url attr_accessor :assertion_consumer_logout_service_binding attr_accessor :issuer + attr_accessor :idp_sso_target_url + attr_accessor :idp_slo_target_url + + # @return [String] IdP Single Sign On Service URL + # + def idp_sso_service_url + val = nil + if @idp_sso_service_url.nil? + if @idp_sso_target_url + val = @idp_sso_target_url + end + else + val = @idp_sso_service_url + end + val + end + + # @return [String] IdP Single Logout Service URL + # + def idp_slo_service_url + val = nil + if @idp_slo_service_url.nil? + if @idp_slo_target_url + val = @idp_slo_target_url + end + else + val = @idp_slo_service_url + end + val + end # @return [String] SP Entity ID # diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index 436a32ae2..ec9bf7bb9 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -24,9 +24,9 @@ def initialize; end settings = idp_metadata_parser.parse(idp_metadata_descriptor) assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id - assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_target_url + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url 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 - assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until @@ -60,7 +60,7 @@ def initialize; end idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new idp_metadata = idp_metadata_descriptor3 settings = idp_metadata_parser.parse(idp_metadata) - assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", settings.idp_sso_target_url + assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", settings.idp_sso_service_url end it "extract SSO endpoint with specific binding" do @@ -69,15 +69,15 @@ def initialize; end options = {} options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'] settings = idp_metadata_parser.parse(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_target_url + assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_service_url options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] settings = idp_metadata_parser.parse(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_target_url + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] settings = idp_metadata_parser.parse(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_target_url + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url end it "uses settings options as hash for overrides" do @@ -117,9 +117,9 @@ def initialize; end metadata = idp_metadata_parser.parse_to_hash(idp_metadata_descriptor) assert_equal "https://hello.example.com/access/saml/idp.xml", metadata[:idp_entity_id] - assert_equal "https://hello.example.com/access/saml/login", metadata[:idp_sso_target_url] + assert_equal "https://hello.example.com/access/saml/login", metadata[:idp_sso_service_url] assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint] - assert_equal "https://hello.example.com/access/saml/logout", metadata[:idp_slo_target_url] + assert_equal "https://hello.example.com/access/saml/logout", metadata[:idp_slo_service_url] assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", metadata[:name_identifier_format] assert_equal ["AuthToken", "SSOStartPage"], metadata[:idp_attribute_names] assert_equal '2014-04-17T18:02:33.910Z', metadata[:valid_until] @@ -153,7 +153,7 @@ def initialize; end idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new idp_metadata = idp_metadata_descriptor3 metadata = idp_metadata_parser.parse_to_hash(idp_metadata) - assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", metadata[:idp_sso_target_url] + assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", metadata[:idp_sso_service_url] end it "extract SSO endpoint with specific binding" do @@ -162,15 +162,15 @@ def initialize; end options = {} options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'] parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", parsed_metadata[:idp_sso_target_url] + assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", parsed_metadata[:idp_sso_service_url] options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_target_url] + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url] options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) - assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_target_url] + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url] end it "ignores a given :settings hash" do @@ -207,8 +207,8 @@ def initialize; end settings = idp_metadata_parser.parse(idp_metadata_descriptor2) assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id - assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_target_url - assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names @@ -239,9 +239,9 @@ def initialize; end settings = idp_metadata_parser.parse_remote(@url) assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id - assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_target_url + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url 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 - assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until @@ -273,9 +273,9 @@ def initialize; end parsed_metadata = idp_metadata_parser.parse_remote_to_hash(@url) assert_equal "https://hello.example.com/access/saml/idp.xml", parsed_metadata[:idp_entity_id] - assert_equal "https://hello.example.com/access/saml/login", parsed_metadata[:idp_sso_target_url] + assert_equal "https://hello.example.com/access/saml/login", parsed_metadata[:idp_sso_service_url] assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint] - assert_equal "https://hello.example.com/access/saml/logout", parsed_metadata[:idp_slo_target_url] + assert_equal "https://hello.example.com/access/saml/logout", parsed_metadata[:idp_slo_service_url] assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", parsed_metadata[:name_identifier_format] assert_equal ["AuthToken", "SSOStartPage"], parsed_metadata[:idp_attribute_names] assert_equal '2014-04-17T18:02:33.910Z', parsed_metadata[:valid_until] @@ -341,9 +341,9 @@ def initialize; end it "should retreive data" do assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", @settings.name_identifier_format - assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_target_url + assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_service_url 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 - assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_service_url assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names assert_equal '2014-04-17T18:02:33.910Z', @settings.valid_until end @@ -434,8 +434,8 @@ def initialize; end assert_equal expected_multi_cert, @settings.idp_cert_multi assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format - assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_target_url - assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_target_url + assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url + assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url end end @@ -479,8 +479,8 @@ def initialize; end assert_equal expected_multi_cert, @settings.idp_cert_multi assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format - assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_target_url - assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_target_url + assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url + assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url end end @@ -519,8 +519,8 @@ def initialize; end assert_nil @settings.idp_cert_multi assert_equal "https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format - assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_target_url - assert_nil @settings.idp_slo_target_url + assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url + assert_nil @settings.idp_slo_service_url end end @@ -587,8 +587,8 @@ def initialize; end assert_equal expected_multi_cert, @settings.idp_cert_multi assert_equal "https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format - assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_target_url - assert_nil @settings.idp_slo_target_url + assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url + assert_nil @settings.idp_slo_service_url end end end diff --git a/test/logout_responses/logoutresponse_fixtures.rb b/test/logout_responses/logoutresponse_fixtures.rb index fe8510fdf..c1110228f 100644 --- a/test/logout_responses/logoutresponse_fixtures.rb +++ b/test/logout_responses/logoutresponse_fixtures.rb @@ -77,8 +77,8 @@ def settings :single_logout_service_url => "http://app.muda.no/sso/consume_logout", :issuer => "http://app.muda.no", :sp_name_qualifier => "http://sso.muda.no", - :idp_sso_target_url => "http://sso.muda.no/sso", - :idp_slo_target_url => "http://sso.muda.no/slo", + :idp_sso_service_url => "http://sso.muda.no/sso", + :idp_slo_service_url => "http://sso.muda.no/slo", :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", } diff --git a/test/request_test.rb b/test/request_test.rb index 68e33984d..31e7d27f5 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -9,7 +9,7 @@ class RequestTest < Minitest::Test let(:settings) { OneLogin::RubySaml::Settings.new } before do - settings.idp_sso_target_url = "http://example.com" + settings.idp_sso_service_url = "http://example.com" end it "create the deflated SAMLRequest URL parameter" do @@ -163,14 +163,14 @@ class RequestTest < Minitest::Test describe "when the target url is not set" do before do - settings.idp_sso_target_url = nil + settings.idp_sso_service_url = nil end it "raises an error with a descriptive message" do err = assert_raises OneLogin::RubySaml::SettingError do OneLogin::RubySaml::Authrequest.new.create(settings) end - assert_match /idp_sso_target_url is not set/, err.message + assert_match /idp_sso_service_url is not set/, err.message end end @@ -184,7 +184,7 @@ class RequestTest < Minitest::Test describe "when the target url contains a query string" do it "create the SAMLRequest parameter correctly" do - settings.idp_sso_target_url = "http://example.com?field=value" + settings.idp_sso_service_url = "http://example.com?field=value" auth_url = OneLogin::RubySaml::Authrequest.new.create(settings) assert_match /^http:\/\/example.com\?field=value&SAMLRequest/, auth_url @@ -228,7 +228,7 @@ class RequestTest < Minitest::Test describe "#create_params when the settings indicate to sign (embebed) the request" do before do settings.compress_request = false - settings.idp_sso_target_url = "http://example.com?field=value" + settings.idp_sso_service_url = "http://example.com?field=value" settings.security[:authn_requests_signed] = true settings.security[:embed_sign] = true settings.certificate = ruby_saml_cert_text @@ -260,7 +260,7 @@ class RequestTest < Minitest::Test before do settings.compress_request = false - settings.idp_sso_target_url = "http://example.com?field=value" + settings.idp_sso_service_url = "http://example.com?field=value" settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" settings.security[:authn_requests_signed] = true settings.security[:embed_sign] = false diff --git a/test/settings_test.rb b/test/settings_test.rb index e2a3b29bc..e9b0a8dbe 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -12,7 +12,7 @@ class SettingsTest < Minitest::Test it "should provide getters and settings" do accessors = [ - :idp_entity_id, :idp_sso_target_url, :idp_slo_target_url, :valid_until, + :idp_entity_id, :idp_sso_target_url, :idp_sso_service_url, :idp_slo_target_url, :idp_slo_service_url, :valid_until, :idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi, :idp_attribute_names, :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding, :single_logout_service_url, :single_logout_service_binding, @@ -38,8 +38,8 @@ class SettingsTest < Minitest::Test :assertion_consumer_service_url => "http://app.muda.no/sso", :issuer => "http://muda.no", :sp_name_qualifier => "http://sso.muda.no", - :idp_sso_target_url => "http://sso.muda.no/sso", - :idp_slo_target_url => "http://sso.muda.no/slo", + :idp_sso_service_url => "http://sso.muda.no/sso", + :idp_slo_service_url => "http://sso.muda.no/slo", :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", :valid_until => '2029-04-16T03:35:08.277Z', :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @@ -125,6 +125,24 @@ class SettingsTest < Minitest::Test end end + describe "#idp_sso_service_url" do + it "when idp_sso_service_url is nil but idp_sso_target_url returns its value" do + @settings.idp_sso_service_url = nil + @settings.idp_sso_target_url = "https://idp.example.com/sso" + + assert_equal "https://idp.example.com/sso", @settings.idp_sso_service_url + end + end + + describe "#idp_slo_service_url" do + it "when idp_slo_service_url is nil but idp_slo_target_url returns its value" do + @settings.idp_slo_service_url = nil + @settings.idp_slo_target_url = "https://idp.example.com/slo" + + assert_equal "https://idp.example.com/slo", @settings.idp_slo_service_url + end + end + describe "#get_idp_cert" do it "returns nil when the cert is an empty string" do @settings.idp_cert = "" diff --git a/test/xml_security_test.rb b/test/xml_security_test.rb index 5844fdc85..bea1feb19 100644 --- a/test/xml_security_test.rb +++ b/test/xml_security_test.rb @@ -236,9 +236,9 @@ class XmlSecurityTest < Minitest::Test describe "XMLSecurity::DSIG" do before do - settings.idp_sso_target_url = "https://idp.example.com/sso" + settings.idp_sso_service_url = "https://idp.example.com/sso" settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - settings.idp_slo_target_url = "https://idp.example.com/slo", + settings.idp_slo_service_url = "https://idp.example.com/slo", settings.sp_entity_id = "https://sp.example.com/saml2" settings.assertion_consumer_service_url = "https://sp.example.com/acs" settings.single_logout_service_url = "https://sp.example.com/sls" From 2e2e9b2a8e2074576dfc6e704b55ac5dc8171c15 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Mon, 25 Jan 2021 19:59:28 +0100 Subject: [PATCH 12/26] Reduce size of built gem by excluding the test folder. See #538 --- ruby-saml.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index 608ce68a9..90d097aa5 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -15,14 +15,14 @@ Gem::Specification.new do |s| "LICENSE", "README.md" ] - s.files = `git ls-files`.split("\n") + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } s.homepage = %q{https://github.com/onelogin/ruby-saml} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.7} s.required_ruby_version = '>= 1.8.7' s.summary = %q{SAML Ruby Tookit} - s.test_files = `git ls-files test/*`.split("\n") + s.test_files = `git ls-files test/*`.split("\x0") # Because runtime dependencies are determined at build time, we cannot make # Nokogiri's version dependent on the Ruby version, even though we would From 533c84ebfc40f8cbac645b6c76ce4949f95d27d6 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Tue, 26 Jan 2021 00:04:48 +0100 Subject: [PATCH 13/26] Minimize Zlib deflate decompression bomb. See #383 --- lib/onelogin/ruby-saml/saml_message.rb | 6 ++++++ test/saml_message_test.rb | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/onelogin/ruby-saml/saml_message.rb b/lib/onelogin/ruby-saml/saml_message.rb index 6f7083cec..d184200a1 100644 --- a/lib/onelogin/ruby-saml/saml_message.rb +++ b/lib/onelogin/ruby-saml/saml_message.rb @@ -22,6 +22,8 @@ class SamlMessage BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z) @@mutex = Mutex.new + MAX_BYTE_SIZE = 250000 + # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema # def self.schema @@ -89,6 +91,10 @@ def valid_saml?(document, soft = true) def decode_raw_saml(saml) return saml unless base64_encoded?(saml) + if saml.bytesize > MAX_BYTE_SIZE + raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected") + end + decoded = decode(saml) begin inflate(decoded) diff --git a/test/saml_message_test.rb b/test/saml_message_test.rb index 6c060c076..7aa494d38 100644 --- a/test/saml_message_test.rb +++ b/test/saml_message_test.rb @@ -52,5 +52,23 @@ class RubySamlTest < Minitest::Test decoded_inflated = saml_message.send(:inflate, decoded) assert response_document_xml, decoded_inflated end + + describe "Prevent Zlib bomb attack" do + it "raises error when SAML Message exceed the allowed bytes" do + prefix= """ + + """ + suffix= """ + ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7 + """ + + data = prefix + "A" * (200000 * 1024) + suffix + bomb = Base64.encode64(Zlib::Deflate.deflate(data, 9)[2..-5]) + assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds " + OneLogin::RubySaml::SamlMessage::MAX_BYTE_SIZE.to_s + " bytes, so was rejected") do + saml_message = OneLogin::RubySaml::SamlMessage.new + saml_message.send(:decode_raw_saml, bomb) + end + end + end end end \ No newline at end of file From 4fe698c995b5f133a1171e7a4f7f3a6a712db529 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Tue, 26 Jan 2021 00:44:19 +0100 Subject: [PATCH 14/26] Update single logout code sample to encourage early session termination. See #519 --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 380a28131..0561c83cb 100644 --- a/README.md +++ b/README.md @@ -644,16 +644,22 @@ def sp_logout_request delete_session else - # Since we created a new SAML request, save the transaction_id - # to compare it with the response we get back logout_request = OneLogin::RubySaml::Logoutrequest.new() - session[:transaction_id] = logout_request.uuid - logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'" + logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'" if settings.name_identifier_value.nil? settings.name_identifier_value = session[:userid] end + # Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34]) + logged_user = session[:userid] + logger.info "Delete session for '#{session[:userid]}'" + delete_session + + # Save the transaction_id to compare it with the response we get back + session[:transaction_id] = logout_request.uuid + session[:logged_out_user] = logged_user + relayState = url_for controller: 'saml', action: 'index' redirect_to(logout_request.create(settings, :RelayState => relayState)) end @@ -681,7 +687,7 @@ def process_logout_response logger.error "The SAML Logout Response is invalid" else # Actually log out this session - logger.info "Delete session for '#{session[:userid]}'" + logger.info "SLO completed for '#{session[:logged_out_user]}'" delete_session end end @@ -690,6 +696,8 @@ end def delete_session session[:userid] = nil session[:attributes] = nil + session[:transaction_id] = nil + session[:logged_out_user] = nil end ``` From 92d6caf70e47f8e7218cb000b8fa9567eb2158cf Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Tue, 26 Jan 2021 18:45:54 +0100 Subject: [PATCH 15/26] See #563 Add ValidUntil and cacheDuration support on Metadata generate method --- README.md | 8 ++++++++ lib/onelogin/ruby-saml/metadata.rb | 10 +++++++++- test/metadata_test.rb | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0561c83cb..21b53c6e8 100644 --- a/README.md +++ b/README.md @@ -765,6 +765,14 @@ class SamlController < ApplicationController end ``` +You can add ValidUntil and CacheDuration to the XML Metadata using instead +```ruby + # Valid until => 2 days from now + # Cache duration = 604800s = 1 week + valid_until = Time.now + 172800 + cache_duration = 604800 + meta.generate(settings, false, valid_until, cache_duration) +``` ## Clock Drift diff --git a/lib/onelogin/ruby-saml/metadata.rb b/lib/onelogin/ruby-saml/metadata.rb index 4d27840c7..d85338035 100644 --- a/lib/onelogin/ruby-saml/metadata.rb +++ b/lib/onelogin/ruby-saml/metadata.rb @@ -15,9 +15,11 @@ class Metadata # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings # @param pretty_print [Boolean] Pretty print or not the response # (No pretty print if you gonna validate the signature) + # @param valid_until [DateTime] Metadata's valid time + # @param cache_duration [Integer] Duration of the cache in seconds # @return [String] XML Metadata of the Service Provider # - def generate(settings, pretty_print=false) + def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil) meta_doc = XMLSecurity::Document.new namespaces = { "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata" @@ -60,6 +62,12 @@ def generate(settings, pretty_print=false) if settings.sp_entity_id root.attributes["entityID"] = settings.sp_entity_id end + if valid_until + root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') + end + if cache_duration + root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" + end if settings.single_logout_service_url sp_sso.add_element "md:SingleLogoutService", { "Binding" => settings.single_logout_service_binding, diff --git a/test/metadata_test.rb b/test/metadata_test.rb index 643e7a2d0..67035db8e 100644 --- a/test/metadata_test.rb +++ b/test/metadata_test.rb @@ -75,6 +75,18 @@ class MetadataTest < Minitest::Test assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") end + it "generates Service Provider Metadata with ValidUntil and CacheDuration" do + valid_until = Time.now + 172800 + cache_duration = 604800 + xml_metadata = OneLogin::RubySaml::Metadata.new.generate(settings, false, valid_until, cache_duration) + start = " Date: Wed, 27 Jan 2021 13:02:43 +0100 Subject: [PATCH 16/26] Add support for cacheDuration at the IdpMetadataParser class, at the end, only a valid_until value will be stored on the settings with the validUntil or cacheDuration related value that expires first --- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 20 +++++- lib/onelogin/ruby-saml/utils.rb | 42 +++++++++++ test/idp_metadata_parser_test.rb | 41 +++++++++++ test/metadata/idp_descriptor_5.xml | 72 +++++++++++++++++++ test/metadata/idp_descriptor_6.xml | 72 +++++++++++++++++++ test/test_helper.rb | 8 +++ 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 test/metadata/idp_descriptor_5.xml create mode 100644 test/metadata/idp_descriptor_6.xml diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index 98c308153..aa855a6c9 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -113,6 +113,16 @@ def parse_remote_to_array(url, validate_cert = true, options = {}) def parse(idp_metadata, options = {}) parsed_metadata = parse_to_hash(idp_metadata, options) + unless parsed_metadata[:cache_duration].nil? + cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration]) + if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until]).to_i + parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ") + end + end + # Remove the cache_duration because on the settings + # we only gonna suppot valid_until + parsed_metadata.delete(:cache_duration) + settings = options[:settings] if settings.nil? @@ -217,7 +227,8 @@ def to_hash(options = {}) :idp_cert => nil, :idp_cert_fingerprint => nil, :idp_cert_multi => nil, - :valid_until => valid_until + :valid_until => valid_until, + :cache_duration => cache_duration, }.tap do |response_hash| merge_certificates_into(response_hash) unless certificates.nil? end @@ -241,6 +252,13 @@ def valid_until root.attributes['validUntil'] if root && root.attributes end + # @return [String|nil] 'cacheDuration' attribute of metadata + # + def cache_duration + root = @idpsso_descriptor.root + root.attributes['cacheDuration'] if root && root.attributes + end + # @param binding_priority [Array] # @return [String|nil] SingleSignOnService binding if exists # diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 0e9619d61..8fc96d0d7 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -28,6 +28,48 @@ def self.is_cert_expired(cert) return cert.not_after < Time.now end + # Interprets a ISO8601 duration value relative to a given timestamp. + # + # @param duration [String] The duration, as a string. + # @param timestamp [Integer] The unix timestamp we should apply the + # duration to. Optional, default to the + # current time. + # + # @return [Integer] The new timestamp, after the duration is applied. + # + def self.parse_duration(duration, timestamp=Time.now) + matches = duration.match(/^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$/) + + if matches.nil? + raise Exception.new("Invalid ISO 8601 duration") + end + + durYears = matches[2].to_i + durMonths = matches[3].to_i + durDays = matches[4].to_i + durHours = matches[5].to_i + durMinutes = matches[6].to_i + durSeconds = matches[7].to_f + durWeeks = matches[8].to_i + + if matches[1] == "-" + durYears = -durYears + durMonths = -durMonths + durDays = -durDays + durHours = -durHours + durMinutes = -durMinutes + durSeconds = -durSeconds + durWeeks = -durWeeks + end + + initial_datetime = Time.at(timestamp).to_datetime + final_datetime = initial_datetime.next_year(durYears) + final_datetime = final_datetime.next_month(durMonths) + final_datetime = final_datetime.next_day((7*durWeeks) + durDays) + final_timestamp = final_datetime.to_time.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds + return final_timestamp + end + # Return a properly formatted x509 certificate # # @param cert [String] The original certificate diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index 560da2dae..e9fdba80c 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -320,6 +320,47 @@ def initialize; end end end + describe "parsing metadata with and without ValidUntil and CacheDuration" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + end + + it "if no ValidUntil or CacheDuration return nothing" do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor3) + assert_nil settings.valid_until + end + + it "if ValidUntil and not CacheDuration return ValidUntil value" do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor) + assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until + end + + it "if no ValidUntil but CacheDuration return CacheDuration converted in ValidUntil" do + Timecop.freeze(Time.parse("2020-01-02T10:02:33Z")) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor5) + assert_equal '2020-01-03T11:02:33Z', settings.valid_until + end + end + + it "if ValidUntil and CacheDuration return the sooner timestamp" do + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z")) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-03T11:12:55Z', settings.valid_until + end + + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z")) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-03T11:12:55Z', settings.valid_until + end + + Timecop.freeze(Time.parse("2020-01-03T10:12:55Z")) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-04T18:02:33.910Z', settings.valid_until + end + end + + end + describe "parsing metadata with many entity descriptors" do before do @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new diff --git a/test/metadata/idp_descriptor_5.xml b/test/metadata/idp_descriptor_5.xml new file mode 100644 index 000000000..3bff97306 --- /dev/null +++ b/test/metadata/idp_descriptor_5.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_descriptor_6.xml b/test/metadata/idp_descriptor_6.xml new file mode 100644 index 000000000..fa6e21db0 --- /dev/null +++ b/test/metadata/idp_descriptor_6.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/test_helper.rb b/test/test_helper.rb index 1d42b837d..dc6d3f490 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -172,6 +172,14 @@ def idp_metadata_descriptor4 @idp_metadata_descriptor4 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_4.xml')) end + def idp_metadata_descriptor5 + @idp_metadata_descriptor5 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_5.xml')) + end + + def idp_metadata_descriptor6 + @idp_metadata_descriptor6 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_6.xml')) + end + def no_idp_metadata_descriptor @no_idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'no_idp_descriptor.xml')) end From 3f89d197bc45b327433bafe25c745cb8ed06fa9d Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Wed, 3 Feb 2021 14:57:24 +0100 Subject: [PATCH 17/26] Support customizable statusCode on generated LogoutResponse --- lib/onelogin/ruby-saml/slo_logoutresponse.rb | 28 +++++++++++--------- test/slo_logoutresponse_test.rb | 8 ++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/onelogin/ruby-saml/slo_logoutresponse.rb b/lib/onelogin/ruby-saml/slo_logoutresponse.rb index 827e1d0b4..c7a28fc8c 100644 --- a/lib/onelogin/ruby-saml/slo_logoutresponse.rb +++ b/lib/onelogin/ruby-saml/slo_logoutresponse.rb @@ -27,10 +27,11 @@ def initialize # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response # @param logout_message [String] The Message to be placed as StatusMessage in the logout response # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response # @return [String] Logout Request string that includes the SAMLRequest # - def create(settings, request_id = nil, logout_message = nil, params = {}) - params = create_params(settings, request_id, logout_message, params) + def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) + params = create_params(settings, request_id, logout_message, params, logout_status_code) params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?' url = settings.idp_slo_response_service_url || settings.idp_slo_target_url saml_response = CGI.escape(params.delete("SAMLResponse")) @@ -48,9 +49,10 @@ def create(settings, request_id = nil, logout_message = nil, params = {}) # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response # @param logout_message [String] The Message to be placed as StatusMessage in the logout response # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response # @return [Hash] Parameters # - def create_params(settings, request_id = nil, logout_message = nil, params = {}) + def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) # The method expects :RelayState but sometimes we get 'RelayState' instead. # Based on the HashWithIndifferentAccess value in Rails we could experience # conflicts so this line will solve them. @@ -61,7 +63,7 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}) params.delete('RelayState') end - response_doc = create_logout_response_xml_doc(settings, request_id, logout_message) + response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code) response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values response = "" @@ -97,14 +99,15 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}) # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response # @param logout_message [String] The Message to be placed as StatusMessage in the logout response + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response # @return [String] The SAMLResponse String. # - def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil) - document = create_xml_document(settings, request_id, logout_message) + def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil) + document = create_xml_document(settings, request_id, logout_message, logout_status_code) sign_document(document, settings) end - def create_xml_document(settings, request_id = nil, logout_message = nil) + def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil) time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') response_doc = XMLSecurity::Document.new @@ -124,14 +127,15 @@ def create_xml_document(settings, request_id = nil, logout_message = nil) issuer.text = settings.sp_entity_id end - # add success message + # add status status = root.add_element 'samlp:Status' - # success status code - status_code = status.add_element 'samlp:StatusCode' - status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success' + # status code + status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success' + status_code_elem = status.add_element 'samlp:StatusCode' + status_code_elem.attributes['Value'] = status_code - # success status message + # status message logout_message ||= 'Successfully Signed Out' status_message = status.add_element 'samlp:StatusMessage' status_message.text = logout_message diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index 176fce951..865db63b3 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -65,6 +65,14 @@ class SloLogoutresponseTest < Minitest::Test assert_match /Custom Logout Message<\/samlp:StatusMessage>/, inflated end + it "set a custom logout message and an status on the response" do + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, "Custom Logout Message", {}, "urn:oasis:names:tc:SAML:2.0:status:PartialLogout") + + inflated = decode_saml_response_payload(unauth_url) + assert_match /Custom Logout Message<\/samlp:StatusMessage>/, inflated + assert_match / settings) assert_equal "test", response.attributes[:uid] @@ -1601,6 +1602,7 @@ def generate_audience_error(expected, actual) end it "EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + return unless OpenSSL::Cipher.ciphers.include? 'AES-192-GCM' unsigned_message_aes192gcm_encrypted_signed_assertion = read_response('unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64') response = OneLogin::RubySaml::Response.new(unsigned_message_aes192gcm_encrypted_signed_assertion, :settings => settings) assert_equal "test", response.attributes[:uid] @@ -1608,6 +1610,7 @@ def generate_audience_error(expected, actual) end it "EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + return unless OpenSSL::Cipher.ciphers.include? 'AES-256-GCM' unsigned_message_aes256gcm_encrypted_signed_assertion = read_response('unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64') response = OneLogin::RubySaml::Response.new(unsigned_message_aes256gcm_encrypted_signed_assertion, :settings => settings) assert_equal "test", response.attributes[:uid] From b4cf36dedde1e287b00647ac9ab05919f4bfb12c Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Mon, 8 Feb 2021 17:01:56 +0100 Subject: [PATCH 19/26] Add duration format as a constant --- lib/onelogin/ruby-saml/utils.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 6d3c38899..e3f3ea8b5 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -15,6 +15,7 @@ class Utils DSIG = "http://www.w3.org/2000/09/xmldsig#" XENC = "http://www.w3.org/2001/04/xmlenc#" + DURATION_FORMAT = %r(^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$) # Checks if the x509 cert provided is expired # @@ -38,7 +39,7 @@ def self.is_cert_expired(cert) # @return [Integer] The new timestamp, after the duration is applied. # def self.parse_duration(duration, timestamp=Time.now) - matches = duration.match(/^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$/) + matches = duration.match(DURATION_FORMAT) if matches.nil? raise Exception.new("Invalid ISO 8601 duration") From 46edfba862d5dc2cb1051faadcedde450304f112 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Mon, 8 Feb 2021 17:49:20 +0100 Subject: [PATCH 20/26] Make it compatible with old versions of Ruby --- lib/onelogin/ruby-saml/attributes.rb | 6 +++++- test/attributes_test.rb | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/onelogin/ruby-saml/attributes.rb b/lib/onelogin/ruby-saml/attributes.rb index 34fbd68c4..054084fe3 100644 --- a/lib/onelogin/ruby-saml/attributes.rb +++ b/lib/onelogin/ruby-saml/attributes.rb @@ -124,7 +124,11 @@ def ==(other) def fetch(name) attributes.each_key do |attribute_key| if name.is_a?(Regexp) - return self[attribute_key] if name.match?(attribute_key) + if name.method_exists? :match? + return self[attribute_key] if name.match?(attribute_key) + else + return self[attribute_key] if name.match(attribute_key) + end elsif canonize_name(name) == canonize_name(attribute_key) return self[attribute_key] end diff --git a/test/attributes_test.rb b/test/attributes_test.rb index c89ae24bd..b98b65b98 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -22,6 +22,8 @@ class AttributesTest < Minitest::Test it 'fetches regexp attribute' do assert_equal('Tom', attributes.fetch(/givenname/)) + assert_equal('Tom', attributes.fetch(/gi(.*)/)) + assert_nil(attributes.fetch(/^z.*/)) assert_equal('Hanks', attributes.fetch(/surname/)) end end From 60b020435fd649d689f3a8c9428ace872512ab8f Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Thu, 11 Feb 2021 11:10:04 +0100 Subject: [PATCH 21/26] Use Time.now.utc instead Time.now on parse_duration --- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 2 +- lib/onelogin/ruby-saml/utils.rb | 2 +- test/idp_metadata_parser_test.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index aa855a6c9..638d459e3 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -115,7 +115,7 @@ def parse(idp_metadata, options = {}) unless parsed_metadata[:cache_duration].nil? cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration]) - if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until]).to_i + if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ") end end diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index e3f3ea8b5..4c23aeaf3 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -38,7 +38,7 @@ def self.is_cert_expired(cert) # # @return [Integer] The new timestamp, after the duration is applied. # - def self.parse_duration(duration, timestamp=Time.now) + def self.parse_duration(duration, timestamp=Time.now.utc) matches = duration.match(DURATION_FORMAT) if matches.nil? diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index e9fdba80c..5f30a336f 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -336,24 +336,24 @@ def initialize; end end it "if no ValidUntil but CacheDuration return CacheDuration converted in ValidUntil" do - Timecop.freeze(Time.parse("2020-01-02T10:02:33Z")) do + Timecop.freeze(Time.parse("2020-01-02T10:02:33Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor5) assert_equal '2020-01-03T11:02:33Z', settings.valid_until end end it "if ValidUntil and CacheDuration return the sooner timestamp" do - Timecop.freeze(Time.parse("2020-01-01T10:12:55Z")) do + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) assert_equal '2020-01-03T11:12:55Z', settings.valid_until end - Timecop.freeze(Time.parse("2020-01-01T10:12:55Z")) do + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) assert_equal '2020-01-03T11:12:55Z', settings.valid_until end - Timecop.freeze(Time.parse("2020-01-03T10:12:55Z")) do + Timecop.freeze(Time.parse("2020-01-03T10:12:55Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) assert_equal '2020-01-04T18:02:33.910Z', settings.valid_until end From b538bdf6182cc00e073617a229c81d3c11cc6fcb Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Thu, 11 Feb 2021 14:27:13 +0100 Subject: [PATCH 22/26] Add request_id, response_id and assertion_id --- lib/onelogin/ruby-saml/authrequest.rb | 4 ++++ lib/onelogin/ruby-saml/logoutrequest.rb | 4 ++++ lib/onelogin/ruby-saml/logoutresponse.rb | 4 ++++ lib/onelogin/ruby-saml/response.rb | 13 ++++++++++++- lib/onelogin/ruby-saml/slo_logoutrequest.rb | 4 ++++ lib/onelogin/ruby-saml/slo_logoutresponse.rb | 4 ++++ 6 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb index 621526a1a..d061f994f 100644 --- a/lib/onelogin/ruby-saml/authrequest.rb +++ b/lib/onelogin/ruby-saml/authrequest.rb @@ -24,6 +24,10 @@ def initialize @uuid = OneLogin::RubySaml::Utils.uuid end + def request_id + @uuid + end + # Creates the AuthNRequest string. # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState diff --git a/lib/onelogin/ruby-saml/logoutrequest.rb b/lib/onelogin/ruby-saml/logoutrequest.rb index 8e8fa739e..0187b2f34 100644 --- a/lib/onelogin/ruby-saml/logoutrequest.rb +++ b/lib/onelogin/ruby-saml/logoutrequest.rb @@ -21,6 +21,10 @@ def initialize @uuid = OneLogin::RubySaml::Utils.uuid end + def request_id + @uuid + end + # Creates the Logout Request string. # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState diff --git a/lib/onelogin/ruby-saml/logoutresponse.rb b/lib/onelogin/ruby-saml/logoutresponse.rb index 798891435..47b496e86 100644 --- a/lib/onelogin/ruby-saml/logoutresponse.rb +++ b/lib/onelogin/ruby-saml/logoutresponse.rb @@ -47,6 +47,10 @@ def initialize(response, settings = nil, options = {}) @document = XMLSecurity::SignedDocument.new(@response) end + def response_id + id(document) + end + # Checks if the Status has the "Success" code # @return [Boolean] True if the StatusCode is Sucess # @raise [ValidationError] if soft == false and validation fails diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb index 520beaaeb..3ab23b609 100644 --- a/lib/onelogin/ruby-saml/response.rb +++ b/lib/onelogin/ruby-saml/response.rb @@ -354,6 +354,17 @@ def assertion_encrypted? ).nil? end + def response_id + id(document) + end + + def assertion_id + @assertion_id ||= begin + node = xpath_first_from_signed_assertion("") + node.nil? ? nil : node.attributes['ID'] + end + end + private # Validates the SAML Response (calls several validation methods) @@ -448,7 +459,7 @@ def validate_response_state # @return [Boolean] True if the SAML Response contains an ID, otherwise returns False # def validate_id - unless id(document) + unless response_id return append_error("Missing ID attribute on SAML Response") end diff --git a/lib/onelogin/ruby-saml/slo_logoutrequest.rb b/lib/onelogin/ruby-saml/slo_logoutrequest.rb index fac38f337..22efce984 100644 --- a/lib/onelogin/ruby-saml/slo_logoutrequest.rb +++ b/lib/onelogin/ruby-saml/slo_logoutrequest.rb @@ -47,6 +47,10 @@ def initialize(request, options = {}) @document = REXML::Document.new(@request) end + def request_id + id(document) + end + # Validates the Logout Request with the default values (soft = true) # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. # @return [Boolean] TRUE if the Logout Request is valid diff --git a/lib/onelogin/ruby-saml/slo_logoutresponse.rb b/lib/onelogin/ruby-saml/slo_logoutresponse.rb index c7a28fc8c..5cab121cc 100644 --- a/lib/onelogin/ruby-saml/slo_logoutresponse.rb +++ b/lib/onelogin/ruby-saml/slo_logoutresponse.rb @@ -22,6 +22,10 @@ def initialize @uuid = OneLogin::RubySaml::Utils.uuid end + def response_id + @uuid + end + # Creates the Logout Response string. # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response From 9577d8624a3cbfc1ba4af196a8bd42df8d7eac7d Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 12 Feb 2021 14:11:40 +0100 Subject: [PATCH 23/26] More utc stuff for parse duration --- lib/onelogin/ruby-saml/utils.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 4c23aeaf3..b3b81feca 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -63,11 +63,11 @@ def self.parse_duration(duration, timestamp=Time.now.utc) durWeeks = -durWeeks end - initial_datetime = Time.at(timestamp).to_datetime + initial_datetime = Time.at(timestamp).utc.to_datetime final_datetime = initial_datetime.next_year(durYears) final_datetime = final_datetime.next_month(durMonths) final_datetime = final_datetime.next_day((7*durWeeks) + durDays) - final_timestamp = final_datetime.to_time.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds + final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds return final_timestamp end From 6962bf53c3877a09024f12aa7de22b38edf94f95 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Mon, 15 Feb 2021 16:24:14 +0100 Subject: [PATCH 24/26] Pending utc changes --- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 2 +- test/idp_metadata_parser_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index 638d459e3..a45cd2fc4 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -116,7 +116,7 @@ def parse(idp_metadata, options = {}) unless parsed_metadata[:cache_duration].nil? cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration]) if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i - parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ") + parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ") end end # Remove the cache_duration because on the settings diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index 5f30a336f..8998ebe21 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -338,19 +338,19 @@ def initialize; end it "if no ValidUntil but CacheDuration return CacheDuration converted in ValidUntil" do Timecop.freeze(Time.parse("2020-01-02T10:02:33Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor5) - assert_equal '2020-01-03T11:02:33Z', settings.valid_until + assert_equal '2020-01-03T10:02:33Z', settings.valid_until end end it "if ValidUntil and CacheDuration return the sooner timestamp" do Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) - assert_equal '2020-01-03T11:12:55Z', settings.valid_until + assert_equal '2020-01-03T10:12:55Z', settings.valid_until end Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) - assert_equal '2020-01-03T11:12:55Z', settings.valid_until + assert_equal '2020-01-03T10:12:55Z', settings.valid_until end Timecop.freeze(Time.parse("2020-01-03T10:12:55Z", Time.now.utc)) do From 59b9ade14709ec37bedc489714c90b0f527ec6da Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Wed, 17 Feb 2021 23:08:49 +0100 Subject: [PATCH 25/26] See #545 More specific error messages for signature validation --- lib/onelogin/ruby-saml/response.rb | 11 +++++-- lib/xml_security.rb | 8 ++--- test/response_test.rb | 22 ++++++++++++-- test/xml_security_test.rb | 49 ++++++++++++++++++++++++++---- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb index 3ab23b609..5be271425 100644 --- a/lib/onelogin/ruby-saml/response.rb +++ b/lib/onelogin/ruby-saml/response.rb @@ -847,7 +847,7 @@ def validate_signature end if sig_elements.size != 1 - if sig_elements.size == 0 + if sig_elements.size == 0 append_error("Signed element id ##{doc.signed_element_id} is not found") else append_error("Signed element id ##{doc.signed_element_id} is found more than once") @@ -855,6 +855,7 @@ def validate_signature return append_error(error_msg) end + old_errors = @errors.clone idp_certs = settings.get_idp_cert_multi if idp_certs.nil? || idp_certs[:signing].empty? @@ -878,21 +879,27 @@ def validate_signature valid = false expired = false idp_certs[:signing].each do |idp_cert| - valid = doc.validate_document_with_cert(idp_cert) + valid = doc.validate_document_with_cert(idp_cert, true) if valid if settings.security[:check_idp_cert_expiration] if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert) expired = true end end + + # At least one certificate is valid, restore the old accumulated errors + @errors = old_errors break end + end if expired error_msg = "IdP x509 certificate expired" return append_error(error_msg) end unless valid + # Remove duplicated errors + @errors = @errors.uniq return append_error(error_msg) end end diff --git a/lib/xml_security.rb b/lib/xml_security.rb index c316fe759..86b89ac3b 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -212,7 +212,7 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {}) begin cert = OpenSSL::X509::Certificate.new(cert_text) rescue OpenSSL::X509::CertificateError => _e - return append_error("Certificate Error", soft) + return append_error("Document Certificate Error", soft) end if options[:fingerprint_alg] @@ -224,7 +224,6 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {}) # check cert matches registered idp cert if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase - @errors << "Fingerprint mismatch" return append_error("Fingerprint mismatch", soft) end else @@ -255,12 +254,12 @@ def validate_document_with_cert(idp_cert, soft = true) begin cert = OpenSSL::X509::Certificate.new(cert_text) rescue OpenSSL::X509::CertificateError => _e - return append_error("Certificate Error", soft) + return append_error("Document Certificate Error", soft) end # check saml response cert matches provided idp cert if idp_cert.to_pem != cert.to_pem - return false + return append_error("Certificate of the Signature element does not match provided certificate", soft) end else base64_cert = Base64.encode64(idp_cert.to_pem) @@ -345,7 +344,6 @@ def validate_signature(base64_cert, soft = true) digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value)) unless digests_match?(hash, digest_value) - @errors << "Digest mismatch" return append_error("Digest mismatch", soft) end diff --git a/test/response_test.rb b/test/response_test.rb index 62ef4dbb2..054a98b29 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -897,6 +897,7 @@ def generate_audience_error(expected, actual) settings.idp_cert_fingerprint = signature_fingerprint_1 response.settings = settings assert !response.send(:validate_signature) + assert_includes response.errors, "Fingerprint mismatch" assert_includes response.errors, "Invalid Signature on SAML Response" end @@ -917,15 +918,29 @@ def generate_audience_error(expected, actual) assert_includes response_valid_signed.errors, "IdP x509 certificate expired" end - it "return false when no X509Certificate and the cert provided at settings mismatches" do + it "return false when X509Certificate and the cert provided at settings mismatches" do settings.idp_cert_fingerprint = nil settings.idp_cert = signature_1 response_valid_signed_without_x509certificate.settings = settings assert !response_valid_signed_without_x509certificate.send(:validate_signature) + assert_includes response_valid_signed_without_x509certificate.errors, "Key validation error" assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response" end - it "return true when no X509Certificate and the cert provided at settings matches" do + it "return false when X509Certificate has invalid content" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = ruby_saml_cert_text + content = read_response('response_with_signed_message_and_assertion.xml') + content = content.sub(/.*<\/ds:X509Certificate>/, + "an-invalid-certificate") + response_invalid_x509certificate = OneLogin::RubySaml::Response.new(content) + response_invalid_x509certificate.settings = settings + assert !response_invalid_x509certificate.send(:validate_signature) + assert_includes response_invalid_x509certificate.errors, "Document Certificate Error" + assert_includes response_invalid_x509certificate.errors, "Invalid Signature on SAML Response" + end + + it "return true when X509Certificate and the cert provided at settings matches" do settings.idp_cert_fingerprint = nil settings.idp_cert = ruby_saml_cert_text response_valid_signed_without_x509certificate.settings = settings @@ -953,7 +968,7 @@ def generate_audience_error(expected, actual) :encryption => [] } response_valid_signed.settings = settings - assert response_valid_signed.send(:validate_signature) + res = response_valid_signed.send(:validate_signature) assert_empty response_valid_signed.errors end @@ -965,6 +980,7 @@ def generate_audience_error(expected, actual) } response_valid_signed.settings = settings assert !response_valid_signed.send(:validate_signature) + assert_includes response_valid_signed.errors, "Certificate of the Signature element does not match provided certificate" assert_includes response_valid_signed.errors, "Invalid Signature on SAML Response" end end diff --git a/test/xml_security_test.rb b/test/xml_security_test.rb index bea1feb19..67c61d22c 100644 --- a/test/xml_security_test.rb +++ b/test/xml_security_test.rb @@ -395,6 +395,11 @@ class XmlSecurityTest < Minitest::Test end describe '#validate_document_with_cert' do + let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') } + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + describe 'with invalid document ' do describe 'when certificate is invalid' do let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') @@ -408,13 +413,8 @@ class XmlSecurityTest < Minitest::Test end end - describe 'with valid document ' do + describe 'with valid document' do describe 'when response has cert' do - let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') } - let(:document) { OneLogin::RubySaml::Response.new(document_data).document } - let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } - let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } - it 'is valid' do assert document.validate_document_with_cert(idp_cert), 'Document should be valid' end @@ -429,6 +429,43 @@ class XmlSecurityTest < Minitest::Test end end end + + describe 'when response has no cert but you have local cert' do + let(:document_data) { response_document_valid_signed_without_x509certificate } + + it 'is valid' do + assert document.validate_document_with_cert(idp_cert), 'Document should be valid' + end + end + + describe 'when response cert is invalid' do + let(:document_data) do + contents = read_response('response_with_signed_message_and_assertion.xml') + contents.sub(/.*<\/ds:X509Certificate>/, + "an-invalid-certificate") + end + + it 'is not valid' do + assert !document.validate_document_with_cert(idp_cert), 'Document should be valid' + assert_equal(["Document Certificate Error"], document.errors) + end + end + + describe 'when response cert is different from idp cert' do + let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text2) } + + it 'is not valid' do + exception = assert_raises(OneLogin::RubySaml::ValidationError) do + document.validate_document_with_cert(idp_cert, false) + end + assert_equal("Certificate of the Signature element does not match provided certificate", exception.message) + end + + it 'is not valid (soft = true)' do + document.validate_document_with_cert(idp_cert) + assert_equal(["Certificate of the Signature element does not match provided certificate"], document.errors) + end + end end end end From d49fde6810fe29712c2081df5c872be10844ddca Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Thu, 18 Feb 2021 13:27:16 +0100 Subject: [PATCH 26/26] Release 1.12.0 --- README.md | 4 ++++ changelog.md | 18 ++++++++++++++++++ lib/onelogin/ruby-saml/version.rb | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21b53c6e8..0b30924a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.svg)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master)](https://coveralls.io/r/onelogin/ruby-saml?branch=master) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml) +## Updating from 1.11.x to 1.12.0 +Version `1.12.0` adds support for gcm algorithm and +change/adds specific error messages for signature validations + ## Updating from 1.10.x to 1.11.0 Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`. There are two new security settings: `settings.security[:check_idp_cert_expiration]` and `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the IdP or SP X.509 certificate has expired, respectively. diff --git a/changelog.md b/changelog.md index f04c9ed13..acf83293c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,23 @@ # RubySaml Changelog +### 1.12.0 (Feb 18, 2021) +* Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions +* Parse & return SLO ResponseLocation in IDPMetadataParser & Settings +* Adding idp_sso_service_url and idp_slo_service_url settings +* [#536](https://github.com/onelogin/ruby-saml/pull/536) Adding feth method to be able retrieve attributes based on regex +* Reduce size of built gem by excluding the test folder +* Improve protection on Zlib deflate decompression bomb attack. +* Add ValidUntil and cacheDuration support on Metadata generator +* Add support for cacheDuration at the IdpMetadataParser +* Support customizable statusCode on generated LogoutResponse +* [#545](https://github.com/onelogin/ruby-saml/pull/545) More specific error messages for signature validation +* Support Process Transform +* Raise SettingError if invoking an action with no endpoint defined on the settings +* Made IdpMetadataParser more extensible for subclasses +*[#548](https://github.com/onelogin/ruby-saml/pull/548) Add :skip_audience option +* [#555](https://github.com/onelogin/ruby-saml/pull/555) Define 'soft' variable to prevent exception when doc cert is invalid +* Improve documentation + ### 1.11.0 (Jul 24, 2019) * Deprecate settings.issuer in favor of settings.sp_entity_id diff --git a/lib/onelogin/ruby-saml/version.rb b/lib/onelogin/ruby-saml/version.rb index 60bf948ea..b3a33a341 100644 --- a/lib/onelogin/ruby-saml/version.rb +++ b/lib/onelogin/ruby-saml/version.rb @@ -1,5 +1,5 @@ module OneLogin module RubySaml - VERSION = '1.11.0' + VERSION = '1.12.0' end end