ruby-saml 1.7.2 → 1.8.0

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

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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