ruby-saml 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58b2d78436fa5304bc67e40cd5f4a0cc766b4301
4
- data.tar.gz: b2445dcda50eeb89ce6fe2b9b285c5021755e189
3
+ metadata.gz: 3e2594fe194305df6e9fae1f6d2ec5f642155fec
4
+ data.tar.gz: c0493eb47ce432fff7575241117d69860a5d1a36
5
5
  SHA512:
6
- metadata.gz: 5b90724f90916e973182de8bf2d009aeabfbc356acb167e927a63d1e360b831e540b25602ba4bb1d79b74fd6e07889860fda5f4620eeb31f76fdc9b585bd13f0
7
- data.tar.gz: cd3750758fa6326bfe6be73552480b1f37994ba137eb048750afbd093d7ee4b23afadcbc1869bfdd49f9b77bdf29f629b0fc6963d814e3cdf54ad72281053999
6
+ metadata.gz: 941cba217d85b8b05b51d22d39f2136ed62ac1a00ce5f52166fbb76a0e82681d0d4ea565d6990476eaaf563af9b3be676df6adb47dce1134235928c21614f149
7
+ data.tar.gz: 972bb6d3d9f71b5278035928a2188de402ff730e9e83d2a9be7e49a1075d08ca76e8c866263415170bdd439071ff956f0c283edb531410a34dd01de8e23d0aaf
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # 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%0A)](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml)
2
2
 
3
+ ## Updating from 1.7.X to 1.8.0
4
+ On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState param will not generate a URL with an empty RelayState parameter anymore. It also changes the invalid audience error message.
5
+
3
6
  ## Updating from 1.6.0 to 1.7.0
4
7
 
5
8
  Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability.
@@ -41,6 +44,10 @@ value.
41
44
  If you want to skip that validation, add the :skip_recipient_check option to the
42
45
  initialize method of the Response object.
43
46
 
47
+ Parsing metadata that contains more than one certificate will propagate the
48
+ idp_cert_multi property rather than idp_cert. See [signature validation
49
+ section](#signature-validation) for details.
50
+
44
51
  ## Updating from 1.3.x to 1.4.X
45
52
 
46
53
  Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
@@ -235,9 +242,10 @@ def saml_settings
235
242
  end
236
243
  ```
237
244
 
238
- Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. For example, you can skip the `Conditions`, `Recipient`, or the `SubjectConfirmation` validations by initializing the response with different options:
245
+ Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation` validations by initializing the response with different options:
239
246
 
240
247
  ```ruby
248
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
241
249
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
242
250
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
243
251
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doens't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
@@ -1,5 +1,14 @@
1
1
  # RubySaml Changelog
2
2
 
3
+ ### 1.8.0 (April 23, 2018)
4
+ * [#437](https://github.com/onelogin/ruby-saml/issues/437) Creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState should not send empty RelayState URL param
5
+ * [#454](https://github.com/onelogin/ruby-saml/pull/454) Added Response available options
6
+ * [#453](https://github.com/onelogin/ruby-saml/pull/453) Raise a more descriptive exception if idp_sso_target_url is missing
7
+ * [#452](https://github.com/onelogin/ruby-saml/pull/452) Fix behavior of skip_conditions flag on Response
8
+ * [#449](https://github.com/onelogin/ruby-saml/pull/449) Add ability to skip authnstatement validation
9
+ * Clear cached values to be able to use IdpMetadataParser more than once
10
+ * Updated invalid audience error message
11
+
3
12
  ### 1.7.2 (Feb 28, 2018)
4
13
  * [#446](https://github.com/onelogin/ruby-saml/pull/446) Normalize text returned by OneLogin::RubySaml::Utils.element_text
5
14
 
@@ -36,6 +36,7 @@ module OneLogin
36
36
  params.each_pair do |key, value|
37
37
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
38
38
  end
39
+ raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
39
40
  @login_url = settings.idp_sso_target_url + request_params
40
41
  end
41
42
 
@@ -50,6 +51,11 @@ module OneLogin
50
51
  # conflicts so this line will solve them.
51
52
  relay_state = params[:RelayState] || params['RelayState']
52
53
 
54
+ if relay_state.nil?
55
+ params.delete(:RelayState)
56
+ params.delete('RelayState')
57
+ end
58
+
53
59
  request_doc = create_authentication_xml_doc(settings)
54
60
  request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
55
61
 
@@ -157,7 +163,7 @@ module OneLogin
157
163
 
158
164
  def sign_document(document, settings)
159
165
  # embed signature
160
- if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
166
+ if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
161
167
  private_key = settings.get_sp_key
162
168
  cert = settings.get_sp_cert
163
169
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -101,6 +101,8 @@ module OneLogin
101
101
  @document = REXML::Document.new(idp_metadata)
102
102
  @options = options
103
103
  @entity_descriptor = nil
104
+ @certificates = nil
105
+ @fingerprint = nil
104
106
 
105
107
  if idpsso_descriptor.nil?
106
108
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
@@ -47,6 +47,11 @@ module OneLogin
47
47
  # conflicts so this line will solve them.
48
48
  relay_state = params[:RelayState] || params['RelayState']
49
49
 
50
+ if relay_state.nil?
51
+ params.delete(:RelayState)
52
+ params.delete('RelayState')
53
+ end
54
+
50
55
  request_doc = create_logout_request_xml_doc(settings)
51
56
  request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
52
57
 
@@ -30,6 +30,15 @@ module OneLogin
30
30
 
31
31
  attr_accessor :soft
32
32
 
33
+ # Response available options
34
+ # This is not a whitelist to allow people extending OneLogin::RubySaml:Response
35
+ # and pass custom options
36
+ AVAILABLE_OPTIONS = [
37
+ :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
38
+ :skip_destination, :skip_recipient_check, :skip_subject_confirmation
39
+ ]
40
+ # TODO: Update the comment on initialize to describe every option
41
+
33
42
  # Constructs the SAML Response. A Response Object that is an extension of the SamlMessage class.
34
43
  # @param response [String] A UUEncoded SAML response from the IdP.
35
44
  # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
@@ -583,7 +592,8 @@ module OneLogin
583
592
  return true if audiences.empty? || settings.issuer.nil? || settings.issuer.empty?
584
593
 
585
594
  unless audiences.include? settings.issuer
586
- error_msg = "#{settings.issuer} is not a valid audience for this Response - Valid audiences: #{audiences.join(',')}"
595
+ s = audiences.count > 1 ? 's' : '';
596
+ error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.issuer}"
587
597
  return append_error(error_msg)
588
598
  end
589
599
 
@@ -615,10 +625,13 @@ module OneLogin
615
625
  end
616
626
 
617
627
  # Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
628
+ # (If the response was initialized with the :skip_conditions option, this validation is skipped)
618
629
  # If fails, the error is added to the errors array
619
630
  # @return [Boolean] True if there is a conditions element and is unique
620
631
  #
621
632
  def validate_one_conditions
633
+ return true if options[:skip_conditions]
634
+
622
635
  conditions_nodes = xpath_from_signed_assertion('/a:Conditions')
623
636
  unless conditions_nodes.size == 1
624
637
  error_msg = "The Assertion must include one Conditions element"
@@ -633,6 +646,8 @@ module OneLogin
633
646
  # @return [Boolean] True if there is a authnstatement element and is unique
634
647
  #
635
648
  def validate_one_authnstatement
649
+ return true if options[:skip_authnstatement]
650
+
636
651
  authnstatement_nodes = xpath_from_signed_assertion('/a:AuthnStatement')
637
652
  unless authnstatement_nodes.size == 1
638
653
  error_msg = "The Assertion must include one AuthnStatement element"
@@ -53,6 +53,11 @@ module OneLogin
53
53
  # conflicts so this line will solve them.
54
54
  relay_state = params[:RelayState] || params['RelayState']
55
55
 
56
+ if relay_state.nil?
57
+ params.delete(:RelayState)
58
+ params.delete('RelayState')
59
+ end
60
+
56
61
  response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
57
62
  response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
58
63
 
@@ -108,7 +113,7 @@ module OneLogin
108
113
  issuer = root.add_element "saml:Issuer"
109
114
  issuer.text = settings.issuer
110
115
  end
111
-
116
+
112
117
  # add success message
113
118
  status = root.add_element 'samlp:Status'
114
119
 
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.7.2'
3
+ VERSION = '1.8.0'
4
4
  end
5
5
  end
@@ -185,6 +185,17 @@ class IdpMetadataParserTest < Minitest::Test
185
185
  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]
186
186
  assert_nil parsed_metadata[:security]
187
187
  end
188
+
189
+ it "can extract certificates multiple times in sequence" do
190
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
191
+ idp_metadata1 = idp_metadata_descriptor
192
+ idp_metadata2 = idp_metadata_descriptor4
193
+ metadata1 = idp_metadata_parser.parse_to_hash(idp_metadata1)
194
+ metadata2 = idp_metadata_parser.parse_to_hash(idp_metadata2)
195
+
196
+ assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata1[:idp_cert_fingerprint]
197
+ assert_equal "CD:2B:2B:DA:FF:F5:DB:64:10:7C:AC:FD:FE:0F:CB:5D:73:5F:16:07", metadata2[:idp_cert_fingerprint]
198
+ end
188
199
  end
189
200
 
190
201
  describe "parsing an IdP descriptor file with multiple signing certs" do
@@ -28,6 +28,20 @@ class RequestTest < Minitest::Test
28
28
  assert_match /&foo=bar$/, unauth_url
29
29
  end
30
30
 
31
+ it "RelayState cases" do
32
+ unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => nil })
33
+ assert !unauth_url.include?('RelayState')
34
+
35
+ unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => "http://example.com" })
36
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
37
+
38
+ unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => nil })
39
+ assert !unauth_url.include?('RelayState')
40
+
41
+ unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => "http://example.com" })
42
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
43
+ end
44
+
31
45
  it "set sessionindex" do
32
46
  settings.idp_slo_target_url = "http://example.com"
33
47
  sessionidx = OneLogin::RubySaml::Utils.uuid
@@ -0,0 +1,72 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
3
+ <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
4
+ <md:KeyDescriptor use="signing">
5
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
6
+ <ds:X509Data>
7
+ <ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
8
+ BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
9
+ aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
10
+ T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
11
+ MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
12
+ Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
13
+ BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
14
+ DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
15
+ 3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
16
+ tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
17
+ VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
18
+ L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
19
+ 1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
20
+ BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
21
+ pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
22
+ VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
23
+ DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
24
+ FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
25
+ AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
26
+ GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
27
+ hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
28
+ vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
29
+ MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
30
+ WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
31
+ </ds:X509Data>
32
+ </ds:KeyInfo>
33
+ </md:KeyDescriptor>
34
+ <md:KeyDescriptor use="encryption">
35
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
36
+ <ds:X509Data>
37
+ <ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
38
+ BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
39
+ aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
40
+ T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
41
+ MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
42
+ Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
43
+ BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
44
+ DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
45
+ 3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
46
+ tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
47
+ VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
48
+ L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
49
+ 1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
50
+ BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
51
+ pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
52
+ VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
53
+ DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
54
+ FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
55
+ AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
56
+ GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
57
+ hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
58
+ vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
59
+ MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
60
+ WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
61
+ </ds:X509Data>
62
+ </ds:KeyInfo>
63
+ </md:KeyDescriptor>
64
+ <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
65
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
66
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
67
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
68
+ <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
69
+ <saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
70
+ <saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
71
+ </md:IDPSSODescriptor>
72
+ </md:EntityDescriptor>
@@ -129,6 +129,33 @@ class RequestTest < Minitest::Test
129
129
  assert_match /&hello=$/, auth_url
130
130
  end
131
131
 
132
+ it "RelayState cases" do
133
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :RelayState => nil })
134
+ assert !auth_url.include?('RelayState')
135
+
136
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :RelayState => "http://example.com" })
137
+ assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
138
+
139
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { 'RelayState' => nil })
140
+ assert !auth_url.include?('RelayState')
141
+
142
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { 'RelayState' => "http://example.com" })
143
+ assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
144
+ end
145
+
146
+ describe "when the target url is not set" do
147
+ before do
148
+ settings.idp_sso_target_url = nil
149
+ end
150
+
151
+ it "raises an error with a descriptive message" do
152
+ err = assert_raises RuntimeError do
153
+ OneLogin::RubySaml::Authrequest.new.create(settings)
154
+ end
155
+ assert_match /idp_sso_target_url is not set/, err.message
156
+ end
157
+ end
158
+
132
159
  describe "when the target url doesn't contain a query string" do
133
160
  it "create the SAMLRequest parameter correctly" do
134
161
 
@@ -222,7 +249,7 @@ class RequestTest < Minitest::Test
222
249
  settings.certificate = ruby_saml_cert_text
223
250
  settings.private_key = ruby_saml_key_text
224
251
  end
225
-
252
+
226
253
  it "create a signature parameter with RSA_SHA1 and validate it" do
227
254
  settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
228
255
 
@@ -255,7 +282,7 @@ class RequestTest < Minitest::Test
255
282
 
256
283
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
257
284
  assert_equal signature_algorithm, OpenSSL::Digest::SHA256
258
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
285
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
259
286
  end
260
287
  end
261
288
 
@@ -23,7 +23,9 @@ class RubySamlTest < Minitest::Test
23
23
  let(:response_no_version) { OneLogin::RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) }
24
24
  let(:response_multi_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) }
25
25
  let(:response_no_conditions) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64")) }
26
+ let(:response_no_conditions_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64"), { :skip_conditions => true }) }
26
27
  let(:response_no_authnstatement) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64")) }
28
+ let(:response_no_authnstatement_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64"), {:skip_authnstatement => true}) }
27
29
  let(:response_empty_destination) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64")) }
28
30
  let(:response_empty_destination_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64"), {:skip_destination => true}) }
29
31
  let(:response_no_status) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) }
@@ -54,10 +56,22 @@ class RubySamlTest < Minitest::Test
54
56
  let(:response_invalid_signature_position) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_signature_position.xml.base64")) }
55
57
  let(:response_encrypted_nameid) { OneLogin::RubySaml::Response.new(response_document_encrypted_nameid) }
56
58
 
59
+ def generate_audience_error(expected, actual)
60
+ s = actual.count > 1 ? 's' : '';
61
+ return "Invalid Audience#{s}. The audience#{s} #{actual.join(',')}, did not match the expected audience #{expected}"
62
+ end
63
+
57
64
  it "raise an exception when response is initialized with nil" do
58
65
  assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
59
66
  end
60
67
 
68
+ it "not filter available options only" do
69
+ options = { :skip_destination => true, :foo => :bar }
70
+ response = OneLogin::RubySaml::Response.new(response_document_valid_signed, options)
71
+ assert_includes response.options.keys, :skip_destination
72
+ assert_includes response.options.keys, :foo
73
+ end
74
+
61
75
  it "be able to parse a document which contains ampersands" do
62
76
  XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
63
77
  OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
@@ -82,7 +96,32 @@ class RubySamlTest < Minitest::Test
82
96
  it "receives the full AttributeValue when there is an injected comment" do
83
97
  assert_equal "smith", @response.attributes["surname"]
84
98
  end
99
+ end
100
+
101
+ describe "Another test to prevent with comment attack (VU#475445)" do
102
+ before do
103
+ @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack2.xml.base64'), {:skip_recipient_check => true })
104
+ @response.settings = settings
105
+ @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
106
+ end
107
+
108
+ it "receives the full NameID when there is an injected comment, validates the response" do
109
+ assert_equal "test@onelogin.com", @response.name_id
110
+ end
111
+ end
85
112
 
113
+ describe "Another test with CDATA injected" do
114
+ before do
115
+ @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack3.xml.base64'), {:skip_recipient_check => true })
116
+ @response.settings = settings
117
+ @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
118
+ end
119
+
120
+ it "it normalizes CDATA but reject SAMLResponse due signature invalidation" do
121
+ assert_equal "test@onelogin.com.evil.com", @response.name_id
122
+ assert !@response.is_valid?
123
+ assert_includes @response.errors, "Invalid Signature on SAML Response"
124
+ end
86
125
  end
87
126
 
88
127
  describe "Prevent XEE attack" do
@@ -223,7 +262,7 @@ class RubySamlTest < Minitest::Test
223
262
  settings.issuer = 'invalid'
224
263
  response_valid_signed.settings = settings
225
264
  response_valid_signed.soft = false
226
- error_msg = "#{response_valid_signed.settings.issuer} is not a valid audience for this Response - Valid audiences: https://someone.example.com/audience"
265
+ error_msg = generate_audience_error(response_valid_signed.settings.issuer, ['https://someone.example.com/audience'])
227
266
  assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
228
267
  response_valid_signed.is_valid?
229
268
  end
@@ -379,7 +418,8 @@ class RubySamlTest < Minitest::Test
379
418
  settings.issuer = 'invalid'
380
419
  response_valid_signed.settings = settings
381
420
  response_valid_signed.is_valid?
382
- assert_includes response_valid_signed.errors, "#{response_valid_signed.settings.issuer} is not a valid audience for this Response - Valid audiences: https://someone.example.com/audience"
421
+
422
+ assert_includes response_valid_signed.errors, generate_audience_error(response_valid_signed.settings.issuer, ['https://someone.example.com/audience'])
383
423
  end
384
424
 
385
425
  it "return false when no ID present in the SAML Response" do
@@ -415,7 +455,7 @@ class RubySamlTest < Minitest::Test
415
455
  response_invalid_subjectconfirmation_recipient.settings = settings
416
456
  collect_errors = true
417
457
  response_invalid_subjectconfirmation_recipient.is_valid?(collect_errors)
418
- assert_includes response_invalid_subjectconfirmation_recipient.errors, "invalid is not a valid audience for this Response - Valid audiences: http://stuff.com/endpoints/metadata.php"
458
+ assert_includes response_invalid_subjectconfirmation_recipient.errors, generate_audience_error('invalid', ['http://stuff.com/endpoints/metadata.php'])
419
459
  assert_includes response_invalid_subjectconfirmation_recipient.errors, "Invalid Signature on SAML Response"
420
460
  end
421
461
  end
@@ -440,7 +480,7 @@ class RubySamlTest < Minitest::Test
440
480
  response.settings = settings
441
481
  response.settings.issuer = 'invalid_audience'
442
482
  assert !response.send(:validate_audience)
443
- assert_includes response.errors, "#{response.settings.issuer} is not a valid audience for this Response - Valid audiences: {audience}"
483
+ assert_includes response.errors, generate_audience_error(response.settings.issuer, ['{audience}'])
444
484
  end
445
485
  end
446
486
 
@@ -626,7 +666,7 @@ class RubySamlTest < Minitest::Test
626
666
  response_invalid_audience.settings = settings
627
667
  response_invalid_audience.settings.issuer = "https://invalid.example.com/audience"
628
668
  assert !response_invalid_audience.send(:validate_audience)
629
- assert_includes response_invalid_audience.errors, "#{response_invalid_audience.settings.issuer} is not a valid audience for this Response - Valid audiences: http://invalid.audience.com"
669
+ assert_includes response_invalid_audience.errors, generate_audience_error(response_invalid_audience.settings.issuer, ['http://invalid.audience.com'])
630
670
  end
631
671
  end
632
672
 
@@ -958,6 +998,11 @@ class RubySamlTest < Minitest::Test
958
998
  response.soft = true
959
999
  assert response.send(:validate_one_conditions)
960
1000
  end
1001
+
1002
+ it "return true when no conditions are present and skip_conditions is true" do
1003
+ response_no_conditions_with_skip.soft = true
1004
+ assert response_no_conditions_with_skip.send(:validate_one_conditions)
1005
+ end
961
1006
  end
962
1007
 
963
1008
  describe "#check_one_authnstatement" do
@@ -971,6 +1016,12 @@ class RubySamlTest < Minitest::Test
971
1016
  response.soft = true
972
1017
  assert response.send(:validate_one_authnstatement)
973
1018
  end
1019
+
1020
+ it "return true when SAML Response is empty but skip_authstatement option is used" do
1021
+ response_no_authnstatement_with_skip.soft = true
1022
+ assert response_no_authnstatement_with_skip.send(:validate_one_authnstatement)
1023
+ assert_empty response_empty_destination_with_skip.errors
1024
+ end
974
1025
  end
975
1026
 
976
1027
  describe "#check_conditions" do
@@ -0,0 +1 @@
1
+ PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhiYzgyNmFmZC1lOWZlLWQzZmItZDg3NC1jNDcwMGMzZWYwYzgiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkl6NFpRbHMzQUpaRGIzczh2Y1VYLzNSYytGUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+UWhLSm1vbnlzUDFxbW5hN1MrZUUxTGMycktBampDMk9HclFPZ1NqUHBUb2N1bVE2aFlIa3pUU1pyN3QvSS9LVE9TdkhDUXFEMXJoNGxTMGpEUC9FdUhOQUN0azlZN2xsMlV5Z3U3MkwrYkZ0cVoyOURuOXJMa1NkR3JpK0k3SGh4TDM2N2RmQVNTaDYrc3k3V2V2RWRrTWZ3ZURRMkFYL3NhNkJCR2d6N1RFPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdDwhLS0gYXR0YWNrIC0tPkBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
@@ -0,0 +1 @@
1
+ PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhiYzgyNmFmZC1lOWZlLWQzZmItZDg3NC1jNDcwMGMzZWYwYzgiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkl6NFpRbHMzQUpaRGIzczh2Y1VYLzNSYytGUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+UWhLSm1vbnlzUDFxbW5hN1MrZUUxTGMycktBampDMk9HclFPZ1NqUHBUb2N1bVE2aFlIa3pUU1pyN3QvSS9LVE9TdkhDUXFEMXJoNGxTMGpEUC9FdUhOQUN0azlZN2xsMlV5Z3U3MkwrYkZ0cVoyOURuOXJMa1NkR3JpK0k3SGh4TDM2N2RmQVNTaDYrc3k3V2V2RWRrTWZ3ZURRMkFYL3NhNkJCR2d6N1RFPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdEBvbmVsb2dpbi5jb208IVtDREFUQVsuZXZpbC5jb21dXT48L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
@@ -37,6 +37,20 @@ class SloLogoutresponseTest < Minitest::Test
37
37
  assert_match /&RelayState=http%3A%2F%2Fidp.example.com$/, unauth_url
38
38
  end
39
39
 
40
+ it "RelayState cases" do
41
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => nil })
42
+ assert !unauth_url.include?('RelayState')
43
+
44
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => "http://example.com" })
45
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
46
+
47
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { 'RelayState' => nil })
48
+ assert !unauth_url.include?('RelayState')
49
+
50
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { 'RelayState' => "http://example.com" })
51
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
52
+ end
53
+
40
54
  it "set InResponseTo to the ID from the logout request" do
41
55
  unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id)
42
56
 
@@ -117,15 +117,15 @@ class Minitest::Test
117
117
  end
118
118
 
119
119
  def signed_message_encrypted_unsigned_assertion
120
- @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64'))
120
+ @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64'))
121
121
  end
122
122
 
123
123
  def signed_message_encrypted_signed_assertion
124
- @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64'))
124
+ @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64'))
125
125
  end
126
126
 
127
127
  def unsigned_message_encrypted_signed_assertion
128
- @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64'))
128
+ @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64'))
129
129
  end
130
130
 
131
131
  def unsigned_message_encrypted_unsigned_assertion
@@ -145,7 +145,7 @@ class Minitest::Test
145
145
  end
146
146
 
147
147
  # certificate used on response_with_undefined_recipient
148
- def signature_1
148
+ def signature_1
149
149
  @signature1 ||= read_certificate("certificate1")
150
150
  end
151
151
 
@@ -166,6 +166,10 @@ class Minitest::Test
166
166
  @idp_metadata_descriptor3 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_3.xml'))
167
167
  end
168
168
 
169
+ def idp_metadata_descriptor4
170
+ @idp_metadata_descriptor4 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_4.xml'))
171
+ end
172
+
169
173
  def no_idp_metadata_descriptor
170
174
  @no_idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'no_idp_descriptor.xml'))
171
175
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-saml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OneLogin LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-28 00:00:00.000000000 Z
11
+ date: 2018-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -220,6 +220,7 @@ files:
220
220
  - test/metadata/idp_descriptor.xml
221
221
  - test/metadata/idp_descriptor_2.xml
222
222
  - test/metadata/idp_descriptor_3.xml
223
+ - test/metadata/idp_descriptor_4.xml
223
224
  - test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml
224
225
  - test/metadata/idp_metadata_multi_certs.xml
225
226
  - test/metadata/idp_metadata_multi_signing_certs.xml
@@ -278,6 +279,8 @@ files:
278
279
  - test/responses/response_eval.xml
279
280
  - test/responses/response_no_cert_and_encrypted_attrs.xml
280
281
  - test/responses/response_node_text_attack.xml.base64
282
+ - test/responses/response_node_text_attack2.xml.base64
283
+ - test/responses/response_node_text_attack3.xml.base64
281
284
  - test/responses/response_unsigned_xml_base64
282
285
  - test/responses/response_with_ampersands.xml
283
286
  - test/responses/response_with_ampersands.xml.base64
@@ -377,6 +380,7 @@ test_files:
377
380
  - test/metadata/idp_descriptor.xml
378
381
  - test/metadata/idp_descriptor_2.xml
379
382
  - test/metadata/idp_descriptor_3.xml
383
+ - test/metadata/idp_descriptor_4.xml
380
384
  - test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml
381
385
  - test/metadata/idp_metadata_multi_certs.xml
382
386
  - test/metadata/idp_metadata_multi_signing_certs.xml
@@ -435,6 +439,8 @@ test_files:
435
439
  - test/responses/response_eval.xml
436
440
  - test/responses/response_no_cert_and_encrypted_attrs.xml
437
441
  - test/responses/response_node_text_attack.xml.base64
442
+ - test/responses/response_node_text_attack2.xml.base64
443
+ - test/responses/response_node_text_attack3.xml.base64
438
444
  - test/responses/response_unsigned_xml_base64
439
445
  - test/responses/response_with_ampersands.xml
440
446
  - test/responses/response_with_ampersands.xml.base64