ruby-saml 0.8.16 → 0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -6
  4. data/Gemfile +2 -12
  5. data/README.md +363 -35
  6. data/Rakefile +14 -0
  7. data/changelog.md +22 -9
  8. data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
  9. data/lib/onelogin/ruby-saml/attributes.rb +26 -64
  10. data/lib/onelogin/ruby-saml/authrequest.rb +47 -89
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
  12. data/lib/onelogin/ruby-saml/logoutrequest.rb +34 -93
  13. data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -24
  14. data/lib/onelogin/ruby-saml/metadata.rb +46 -16
  15. data/lib/onelogin/ruby-saml/response.rb +62 -322
  16. data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +54 -121
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -61
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -84
  20. data/lib/onelogin/ruby-saml/utils.rb +32 -199
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/ruby-saml.rb +5 -2
  23. data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
  24. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  25. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  26. data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
  27. data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
  28. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  29. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  30. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  31. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  32. data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
  33. data/lib/schemas/xml.xsd +287 -0
  34. data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
  35. data/lib/xml_security.rb +83 -235
  36. data/ruby-saml.gemspec +1 -0
  37. data/test/idp_metadata_parser_test.rb +54 -0
  38. data/test/logoutrequest_test.rb +68 -144
  39. data/test/logoutresponse_test.rb +43 -25
  40. data/test/metadata_test.rb +87 -0
  41. data/test/request_test.rb +103 -90
  42. data/test/response_test.rb +181 -471
  43. data/test/responses/idp_descriptor.xml +3 -0
  44. data/test/responses/logoutresponse_fixtures.rb +5 -5
  45. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  46. data/test/responses/response_with_multiple_attribute_values.xml +1 -1
  47. data/test/responses/slo_request.xml +4 -0
  48. data/test/settings_test.rb +25 -112
  49. data/test/slo_logoutrequest_test.rb +41 -44
  50. data/test/slo_logoutresponse_test.rb +87 -167
  51. data/test/test_helper.rb +27 -102
  52. data/test/xml_security_test.rb +114 -337
  53. metadata +34 -84
  54. data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
  55. data/test/certificates/certificate.der +0 -0
  56. data/test/certificates/formatted_certificate +0 -14
  57. data/test/certificates/formatted_chained_certificate +0 -42
  58. data/test/certificates/formatted_private_key +0 -12
  59. data/test/certificates/formatted_rsa_private_key +0 -12
  60. data/test/certificates/invalid_certificate1 +0 -1
  61. data/test/certificates/invalid_certificate2 +0 -1
  62. data/test/certificates/invalid_certificate3 +0 -12
  63. data/test/certificates/invalid_chained_certificate1 +0 -1
  64. data/test/certificates/invalid_private_key1 +0 -1
  65. data/test/certificates/invalid_private_key2 +0 -1
  66. data/test/certificates/invalid_private_key3 +0 -10
  67. data/test/certificates/invalid_rsa_private_key1 +0 -1
  68. data/test/certificates/invalid_rsa_private_key2 +0 -1
  69. data/test/certificates/invalid_rsa_private_key3 +0 -10
  70. data/test/certificates/ruby-saml-2.crt +0 -15
  71. data/test/requests/logoutrequest_fixtures.rb +0 -47
  72. data/test/responses/encrypted_new_attack.xml.base64 +0 -1
  73. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  74. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  75. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  76. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  77. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  78. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  79. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  80. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  81. data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
  82. data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
  83. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  84. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  85. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  86. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  87. data/test/responses/response_wrapped.xml.base64 +0 -150
  88. data/test/responses/valid_response.xml.base64 +0 -1
  89. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  90. data/test/utils_test.rb +0 -231
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ class MetadataTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @settings = OneLogin::RubySaml::Settings.new
7
+ @settings.issuer = "https://example.com"
8
+ @settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
9
+ @settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
10
+ @settings.security[:authn_requests_signed] = false
11
+ end
12
+
13
+ should "generate Service Provider Metadata with X509Certificate" do
14
+ @settings.security[:authn_requests_signed] = true
15
+ @settings.certificate = ruby_saml_cert_text
16
+
17
+ xml_text = OneLogin::RubySaml::Metadata.new.generate(@settings)
18
+
19
+ # assert xml_text can be parsed into an xml doc
20
+ xml_doc = REXML::Document.new(xml_text)
21
+
22
+ spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
23
+ assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
24
+
25
+ cert_node = REXML::XPath.first(xml_doc, "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", {
26
+ "md" => "urn:oasis:names:tc:SAML:2.0:metadata",
27
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
28
+ })
29
+ cert_text = cert_node.text
30
+ cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
31
+ assert_equal ruby_saml_cert.to_der, cert.to_der
32
+ end
33
+
34
+ should "should generate Service Provider Metadata" do
35
+ settings = OneLogin::RubySaml::Settings.new
36
+ settings.issuer = "https://example.com"
37
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
38
+ settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
39
+ settings.security[:authn_requests_signed] = false
40
+
41
+ xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
42
+
43
+ # assert correct xml declaration
44
+ start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
45
+ assert xml_text[0..start.length-1] == start
46
+
47
+ # assert xml_text can be parsed into an xml doc
48
+ xml_doc = REXML::Document.new(xml_text)
49
+
50
+ assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
51
+
52
+ spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
53
+ assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
54
+ assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
55
+ assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
56
+
57
+ assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
58
+
59
+ acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
60
+ assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
61
+ assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
62
+ end
63
+
64
+ should "generate attribute service if configured" do
65
+ settings = OneLogin::RubySaml::Settings.new
66
+ settings.issuer = "https://example.com"
67
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
68
+ settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
69
+ settings.attribute_consuming_service.configure do
70
+ service_name "Test Service"
71
+ add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
72
+ end
73
+
74
+ xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
75
+ xml_doc = REXML::Document.new(xml_text)
76
+ acs = REXML::XPath.first(xml_doc, "//md:AttributeConsumingService")
77
+ assert_equal "true", acs.attribute("isDefault").value
78
+ assert_equal "1", acs.attribute("index").value
79
+ assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
80
+ req_attr = REXML::XPath.first(xml_doc, "//md:RequestedAttribute")
81
+ assert_equal "Name", req_attr.attribute("Name").value
82
+ assert_equal "Name Format", req_attr.attribute("NameFormat").value
83
+ assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
84
+ assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
85
+ end
86
+
87
+ end
@@ -1,15 +1,11 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class RequestTest < Minitest::Test
3
+ class RequestTest < Test::Unit::TestCase
4
4
 
5
- describe "Authrequest" do
6
- let(:settings) { OneLogin::RubySaml::Settings.new }
7
-
8
- before do
5
+ context "Authrequest" do
6
+ should "create the deflated SAMLRequest URL parameter" do
7
+ settings = OneLogin::RubySaml::Settings.new
9
8
  settings.idp_sso_target_url = "http://example.com"
10
- end
11
-
12
- it "create the deflated SAMLRequest URL parameter" do
13
9
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
14
10
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
15
11
  payload = CGI.unescape(auth_url.split("=").last)
@@ -23,7 +19,9 @@ class RequestTest < Minitest::Test
23
19
  assert_match /^<samlp:AuthnRequest/, inflated
24
20
  end
25
21
 
26
- it "create the deflated SAMLRequest URL parameter including the Destination" do
22
+ should "create the deflated SAMLRequest URL parameter including the Destination" do
23
+ settings = OneLogin::RubySaml::Settings.new
24
+ settings.idp_sso_target_url = "http://example.com"
27
25
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
28
26
  payload = CGI.unescape(auth_url.split("=").last)
29
27
  decoded = Base64.decode64(payload)
@@ -36,8 +34,10 @@ class RequestTest < Minitest::Test
36
34
  assert_match /<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated
37
35
  end
38
36
 
39
- it "create the SAMLRequest URL parameter without deflating" do
37
+ should "create the SAMLRequest URL parameter without deflating" do
38
+ settings = OneLogin::RubySaml::Settings.new
40
39
  settings.compress_request = false
40
+ settings.idp_sso_target_url = "http://example.com"
41
41
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
42
42
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
43
43
  payload = CGI.unescape(auth_url.split("=").last)
@@ -46,7 +46,9 @@ class RequestTest < Minitest::Test
46
46
  assert_match /^<samlp:AuthnRequest/, decoded
47
47
  end
48
48
 
49
- it "create the SAMLRequest URL parameter with IsPassive" do
49
+ should "create the SAMLRequest URL parameter with IsPassive" do
50
+ settings = OneLogin::RubySaml::Settings.new
51
+ settings.idp_sso_target_url = "http://example.com"
50
52
  settings.passive = true
51
53
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
52
54
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
@@ -61,8 +63,10 @@ class RequestTest < Minitest::Test
61
63
  assert_match /<samlp:AuthnRequest[^<]* IsPassive='true'/, inflated
62
64
  end
63
65
 
64
- it "create the SAMLRequest URL parameter with ProtocolBinding" do
65
- settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
66
+ should "create the SAMLRequest URL parameter with ProtocolBinding" do
67
+ settings = OneLogin::RubySaml::Settings.new
68
+ settings.idp_sso_target_url = "http://example.com"
69
+ settings.protocol_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
66
70
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
67
71
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
68
72
  payload = CGI.unescape(auth_url.split("=").last)
@@ -76,8 +80,10 @@ class RequestTest < Minitest::Test
76
80
  assert_match /<samlp:AuthnRequest[^<]* ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'/, inflated
77
81
  end
78
82
 
79
- it "create the SAMLRequest URL parameter with ForceAuthn" do
80
- settings.force_authn = true
83
+ should "create the SAMLRequest URL parameter with AttributeConsumingServiceIndex" do
84
+ settings = OneLogin::RubySaml::Settings.new
85
+ settings.idp_sso_target_url = "http://example.com"
86
+ settings.attributes_index = 30
81
87
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
82
88
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
83
89
  payload = CGI.unescape(auth_url.split("=").last)
@@ -87,42 +93,29 @@ class RequestTest < Minitest::Test
87
93
  inflated = zstream.inflate(decoded)
88
94
  zstream.finish
89
95
  zstream.close
90
- assert_match /<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated
96
+ assert_match /<samlp:AuthnRequest[^<]* AttributeConsumingServiceIndex='30'/, inflated
91
97
  end
92
98
 
93
- it "create the SAMLRequest URL parameter with NameID Format" do
94
- settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
99
+ should "create the SAMLRequest URL parameter with ForceAuthn" do
100
+ settings = OneLogin::RubySaml::Settings.new
101
+ settings.idp_sso_target_url = "http://example.com"
102
+ settings.force_authn = true
95
103
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
96
104
  assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
97
- payload = CGI.unescape(auth_url.split("=").last)
98
- decoded = Base64.decode64(payload)
99
- zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
100
- inflated = zstream.inflate(decoded)
101
- zstream.finish
102
- zstream.close
103
-
104
- assert_match /<samlp:NameIDPolicy[^<]* AllowCreate='true'/, inflated
105
- assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
106
- end
105
+ payload = CGI.unescape(auth_url.split("=").last)
106
+ decoded = Base64.decode64(payload)
107
107
 
108
- it "create the SAMLRequest URL parameter with Subject" do
109
- settings.name_identifier_value_requested = "testuser@example.com"
110
- settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
111
- auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
112
- assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
113
- payload = CGI.unescape(auth_url.split("=").last)
114
- decoded = Base64.decode64(payload)
115
- zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
108
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
116
109
  inflated = zstream.inflate(decoded)
117
110
  zstream.finish
118
111
  zstream.close
119
-
120
- assert inflated.include?('<saml:Subject>')
121
- assert inflated.include?("<saml:NameID Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'>testuser@example.com</saml:NameID>")
122
- assert inflated.include?("<saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'/>")
112
+ assert_match /<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated
123
113
  end
124
114
 
125
- it "accept extra parameters" do
115
+ should "accept extra parameters" do
116
+ settings = OneLogin::RubySaml::Settings.new
117
+ settings.idp_sso_target_url = "http://example.com"
118
+
126
119
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
127
120
  assert auth_url =~ /&hello=there$/
128
121
 
@@ -130,15 +123,19 @@ class RequestTest < Minitest::Test
130
123
  assert auth_url =~ /&hello=$/
131
124
  end
132
125
 
133
- describe "when the target url doesn't contain a query string" do
134
- it "create the SAMLRequest parameter correctly" do
126
+ context "when the target url doesn't contain a query string" do
127
+ should "create the SAMLRequest parameter correctly" do
128
+ settings = OneLogin::RubySaml::Settings.new
129
+ settings.idp_sso_target_url = "http://example.com"
130
+
135
131
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
136
132
  assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/
137
133
  end
138
134
  end
139
135
 
140
- describe "when the target url contains a query string" do
141
- it "create the SAMLRequest parameter correctly" do
136
+ context "when the target url contains a query string" do
137
+ should "create the SAMLRequest parameter correctly" do
138
+ settings = OneLogin::RubySaml::Settings.new
142
139
  settings.idp_sso_target_url = "http://example.com?field=value"
143
140
 
144
141
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
@@ -146,84 +143,100 @@ class RequestTest < Minitest::Test
146
143
  end
147
144
  end
148
145
 
149
- describe "#create_params when the settings indicate to sign (embebed) the request" do
150
- before do
146
+ context "when the settings indicate to sign (embebed) the request" do
147
+ should "create a signed request" do
148
+ settings = OneLogin::RubySaml::Settings.new
151
149
  settings.compress_request = false
152
150
  settings.idp_sso_target_url = "http://example.com?field=value"
153
151
  settings.security[:authn_requests_signed] = true
154
152
  settings.security[:embed_sign] = true
155
- settings.certificate = ruby_saml_cert_text
153
+ settings.certificate = ruby_saml_cert_text
156
154
  settings.private_key = ruby_saml_key_text
157
- end
158
155
 
159
- it "create a signed request" do
160
156
  params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
161
157
  request_xml = Base64.decode64(params["SAMLRequest"])
162
158
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
163
- assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], request_xml
159
+ request_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/
160
+ request_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/
164
161
  end
165
162
 
166
- it "create a signed request with 256 digest and signature methods" do
167
- settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
163
+ should "create a signed request with 256 digest and signature methods" do
164
+ settings = OneLogin::RubySaml::Settings.new
165
+ settings.compress_request = false
166
+ settings.idp_sso_target_url = "http://example.com?field=value"
167
+ settings.security[:authn_requests_signed] = true
168
+ settings.security[:embed_sign] = true
169
+ settings.security[:signature_method] = XMLSecurity::Document::SHA256
168
170
  settings.security[:digest_method] = XMLSecurity::Document::SHA512
171
+ settings.certificate = ruby_saml_cert_text
172
+ settings.private_key = ruby_saml_key_text
169
173
 
170
174
  params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
171
-
172
175
  request_xml = Base64.decode64(params["SAMLRequest"])
173
176
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
174
- assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
175
- assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
177
+ request_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/
178
+ request_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha512'\/>/
176
179
  end
177
180
  end
178
181
 
179
- describe "#create_params when the settings indicate to sign the request" do
180
- let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
181
182
 
182
- before do
183
+ context "when the settings indicate to sign the request" do
184
+ should "create a signature parameter" do
185
+ settings = OneLogin::RubySaml::Settings.new
183
186
  settings.compress_request = false
184
187
  settings.idp_sso_target_url = "http://example.com?field=value"
188
+ settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
185
189
  settings.security[:authn_requests_signed] = true
186
190
  settings.security[:embed_sign] = false
187
- settings.certificate = ruby_saml_cert_text
191
+ settings.security[:signature_method] = XMLSecurity::Document::SHA1
192
+ settings.certificate = ruby_saml_cert_text
188
193
  settings.private_key = ruby_saml_key_text
189
- end
190
-
191
- it "create a signature parameter with RSA_SHA1 and validate it" do
192
- settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
193
194
 
194
- params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
195
- assert params['SAMLRequest']
196
- assert params[:RelayState]
195
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
197
196
  assert params['Signature']
198
- assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
197
+ assert params['SigAlg'] == XMLSecurity::Document::SHA1
199
198
 
200
- query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
201
- query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
202
- query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
203
-
204
- signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
205
- assert_equal signature_algorithm, OpenSSL::Digest::SHA1
206
-
207
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
199
+ # signature_method only affects the embedeed signature
200
+ settings.security[:signature_method] = XMLSecurity::Document::SHA256
201
+ params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
202
+ assert params['Signature']
203
+ assert params['SigAlg'] == XMLSecurity::Document::SHA1
208
204
  end
205
+ end
209
206
 
210
- it "create a signature parameter with RSA_SHA256 and validate it" do
211
- settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
212
-
213
- params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
214
- assert params['Signature']
215
- assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
207
+ should "create the saml:AuthnContextClassRef element correctly" do
208
+ settings = OneLogin::RubySaml::Settings.new
209
+ settings.idp_sso_target_url = "http://example.com"
210
+ settings.authn_context = 'secure/name/password/uri'
211
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
212
+ assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
213
+ end
216
214
 
217
- query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
218
- query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
219
- query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
215
+ should "create the saml:AuthnContextClassRef with comparison exact" do
216
+ settings = OneLogin::RubySaml::Settings.new
217
+ settings.idp_sso_target_url = "http://example.com"
218
+ settings.authn_context = 'secure/name/password/uri'
219
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
220
+ assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/
221
+ assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
222
+ end
220
223
 
221
- signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
222
- assert_equal signature_algorithm, OpenSSL::Digest::SHA256
223
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
224
- end
224
+ should "create the saml:AuthnContextClassRef with comparison minimun" do
225
+ settings = OneLogin::RubySaml::Settings.new
226
+ settings.idp_sso_target_url = "http://example.com"
227
+ settings.authn_context = 'secure/name/password/uri'
228
+ settings.authn_context_comparison = 'minimun'
229
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
230
+ assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/
231
+ assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
225
232
  end
226
233
 
234
+ should "create the saml:AuthnContextDeclRef element correctly" do
235
+ settings = OneLogin::RubySaml::Settings.new
236
+ settings.idp_sso_target_url = "http://example.com"
237
+ settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
238
+ auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
239
+ assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
240
+ end
227
241
  end
228
-
229
242
  end
@@ -1,16 +1,13 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class ResponseTest < Minitest::Test
3
+ class RubySamlTest < Test::Unit::TestCase
4
4
 
5
- describe "Response" do
6
- it "raise an exception when response is initialized with nil" do
7
- err = assert_raises(ArgumentError) do
8
- OneLogin::RubySaml::Response.new(nil)
9
- end
10
- assert_equal "Response cannot be nil", err.message
5
+ context "Response" do
6
+ should "raise an exception when response is initialized with nil" do
7
+ assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) }
11
8
  end
12
9
 
13
- it "be able to parse a document which contains ampersands" do
10
+ should "be able to parse a document which contains ampersands" do
14
11
  XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
15
12
  OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true)
16
13
 
@@ -21,135 +18,74 @@ class ResponseTest < Minitest::Test
21
18
  response.validate!
22
19
  end
23
20
 
24
- it "adapt namespace" do
21
+ should "adapt namespace" do
25
22
  response = OneLogin::RubySaml::Response.new(response_document)
26
- assert !response.name_id.nil?
23
+ assert_not_nil response.name_id
27
24
  response = OneLogin::RubySaml::Response.new(response_document_2)
28
- assert !response.name_id.nil?
25
+ assert_not_nil response.name_id
29
26
  response = OneLogin::RubySaml::Response.new(response_document_3)
30
- assert !response.name_id.nil?
27
+ assert_not_nil response.name_id
31
28
  end
32
29
 
33
- it "default to raw input when a response is not Base64 encoded" do
30
+ should "default to raw input when a response is not Base64 encoded" do
34
31
  decoded = Base64.decode64(response_document_2)
35
32
  response = OneLogin::RubySaml::Response.new(decoded)
36
33
  assert response.document
37
34
  end
38
35
 
39
- describe "Assertion" do
40
- it "only retreive an assertion with an ID that matches the signature's reference URI" do
36
+ context "Assertion" do
37
+ should "only retreive an assertion with an ID that matches the signature's reference URI" do
41
38
  response = OneLogin::RubySaml::Response.new(wrapped_response_2)
42
39
  response.stubs(:conditions).returns(nil)
43
40
  settings = OneLogin::RubySaml::Settings.new
44
41
  settings.idp_cert_fingerprint = signature_fingerprint_1
45
42
  response.settings = settings
46
- assert response.name_id.nil?
43
+ assert_nil response.name_id
47
44
  end
48
45
  end
49
46
 
50
- describe "#validate!" do
51
- it "raise when settings not initialized" do
47
+ context "#validate!" do
48
+ should "raise when encountering a condition that prevents the document from being valid" do
52
49
  response = OneLogin::RubySaml::Response.new(response_document)
53
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
54
- response.validate!
55
- end
56
- assert_equal "No settings on response", err.message
57
- end
58
-
59
- it "raise when encountering a condition that prevents the document from being valid" do
60
- response = OneLogin::RubySaml::Response.new(response_document)
61
- response.settings = settings
62
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
63
- response.validate!
64
- end
65
- assert_equal "Current time is on or after NotOnOrAfter condition", err.message
66
- end
67
-
68
- it "raises an exception when no cert or fingerprint provided" do
69
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
70
- response.stubs(:conditions).returns(nil)
71
- settings = OneLogin::RubySaml::Settings.new
72
- response.settings = settings
73
- settings.idp_cert = nil
74
- settings.idp_cert_fingerprint = nil
75
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
50
+ assert_raise(OneLogin::RubySaml::ValidationError) do
76
51
  response.validate!
77
52
  end
78
- assert_equal "No fingerprint or certificate on settings", err.message
79
53
  end
54
+ end
80
55
 
81
- it "raise when no signature" do
82
- response_no_signed_elements = OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64"))
83
- settings.idp_cert_fingerprint = signature_fingerprint_1
84
- response_no_signed_elements.settings = settings
85
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
86
- response_no_signed_elements.validate!
87
- end
88
- assert_equal "Found an unexpected number of Signature Element. SAML Response rejected", err.message
89
- end
90
-
91
- it "raise when multiple signatures" do
92
- response_multiple_signed = OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64"))
93
- settings.idp_cert_fingerprint = signature_fingerprint_1
94
- response_multiple_signed.settings = settings
95
- response_multiple_signed.stubs(:validate_structure).returns(true)
96
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
97
- response_multiple_signed.validate!
98
- end
99
- assert_equal "Duplicated ID. SAML Response rejected", err.message
100
- end
101
-
102
- it "raise when fingerprint missmatch" do
103
- resp_xml = Base64.decode64(response_document_valid_signed)
104
- response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
105
- response.stubs(:conditions).returns(nil)
106
- settings = OneLogin::RubySaml::Settings.new
107
- settings.idp_cert_fingerprint = signature_fingerprint_1
108
- response.settings = settings
109
-
110
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
111
- response.validate!
112
- end
113
- assert_equal 'Fingerprint mismatch', err.message
56
+ context "#validate_structure" do
57
+ should "raise when encountering a condition that prevents the document from being valid" do
58
+ response = OneLogin::RubySaml::Response.new(response_document_2)
59
+ response.send(:validate_structure)
60
+ assert response.errors.include? "Schema validation failed"
114
61
  end
115
-
116
62
  end
117
63
 
118
- describe "#is_valid?" do
119
- it "return false when response is initialized with blank data" do
64
+ context "#is_valid?" do
65
+ should "return false when response is initialized with blank data" do
120
66
  response = OneLogin::RubySaml::Response.new('')
121
67
  assert !response.is_valid?
122
68
  end
123
69
 
124
- it "return false if settings have not been set" do
70
+ should "return false if settings have not been set" do
125
71
  response = OneLogin::RubySaml::Response.new(response_document)
126
72
  assert !response.is_valid?
127
73
  end
128
74
 
129
- it "return false when no cert or fingerprint provided" do
130
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
131
- response.stubs(:conditions).returns(nil)
132
- settings = OneLogin::RubySaml::Settings.new
133
- response.settings = settings
134
- settings.idp_cert = nil
135
- settings.idp_cert_fingerprint = nil
136
- assert !response.is_valid?
137
- end
138
-
139
- it "return true when the response is initialized with valid data" do
140
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
75
+ should "return true when the response is initialized with valid data" do
76
+ response = OneLogin::RubySaml::Response.new(response_document_4)
141
77
  response.stubs(:conditions).returns(nil)
142
78
  assert !response.is_valid?
143
79
  settings = OneLogin::RubySaml::Settings.new
144
80
  assert !response.is_valid?
145
81
  response.settings = settings
146
82
  assert !response.is_valid?
147
- response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
148
- response.validate!
83
+ settings.idp_cert_fingerprint = signature_fingerprint_1
84
+ assert response.is_valid?
149
85
  end
150
86
 
151
- it "should be idempotent when the response is initialized with invalid data" do
152
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
87
+ should "should be idempotent when the response is initialized with invalid data" do
88
+ response = OneLogin::RubySaml::Response.new(response_document_4)
153
89
  response.stubs(:conditions).returns(nil)
154
90
  settings = OneLogin::RubySaml::Settings.new
155
91
  response.settings = settings
@@ -157,58 +93,36 @@ class ResponseTest < Minitest::Test
157
93
  assert !response.is_valid?
158
94
  end
159
95
 
160
- it "should be idempotent when the response is initialized with valid data" do
161
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
96
+ should "should be idempotent when the response is initialized with valid data" do
97
+ response = OneLogin::RubySaml::Response.new(response_document_4)
162
98
  response.stubs(:conditions).returns(nil)
163
99
  settings = OneLogin::RubySaml::Settings.new
164
100
  response.settings = settings
165
- response.settings.idp_cert_fingerprint = signature_fingerprint_valid_res
101
+ settings.idp_cert_fingerprint = signature_fingerprint_1
166
102
  assert response.is_valid?
167
103
  assert response.is_valid?
168
104
  end
169
105
 
170
- it "return true when valid response and using fingerprint" do
171
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
172
- response.stubs(:conditions).returns(nil)
173
- settings = OneLogin::RubySaml::Settings.new
174
- response.settings = settings
175
- settings.idp_cert = nil
176
- settings.idp_cert_fingerprint = "4B:68:C4:53:C7:D9:94:AA:D9:02:5C:99:D5:EF:CF:56:62:87:FE:8D"
177
- assert response.is_valid?
178
- end
179
-
180
- it "return true when valid response using certificate" do
181
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
106
+ should "return true when using certificate instead of fingerprint" do
107
+ response = OneLogin::RubySaml::Response.new(response_document_4)
182
108
  response.stubs(:conditions).returns(nil)
183
109
  settings = OneLogin::RubySaml::Settings.new
184
110
  response.settings = settings
185
- settings.idp_cert = valid_cert
111
+ settings.idp_cert = signature_1
186
112
  assert response.is_valid?
187
113
  end
188
114
 
189
- it "not allow signature wrapping attack" do
115
+ should "not allow signature wrapping attack" do
190
116
  response = OneLogin::RubySaml::Response.new(response_document_4)
191
117
  response.stubs(:conditions).returns(nil)
192
118
  settings = OneLogin::RubySaml::Settings.new
193
119
  settings.idp_cert_fingerprint = signature_fingerprint_1
194
120
  response.settings = settings
195
- assert !response.is_valid?
121
+ assert response.is_valid?
196
122
  assert response.name_id == "test@onelogin.com"
197
123
  end
198
124
 
199
- it "not allow element wrapping attack" do
200
- response_wrapped = OneLogin::RubySaml::Response.new(response_document_wrapped)
201
- response_wrapped.stubs(:conditions).returns(nil)
202
- response_wrapped.stubs(:validate_subject_confirmation).returns(true)
203
- settings = OneLogin::RubySaml::Settings.new
204
- response_wrapped.settings = settings
205
- response_wrapped.settings.idp_cert_fingerprint = signature_fingerprint_1
206
-
207
- assert !response_wrapped.is_valid?
208
- assert_nil response_wrapped.name_id
209
- end
210
-
211
- it "support dynamic namespace resolution on signature elements" do
125
+ should "support dynamic namespace resolution on signature elements" do
212
126
  response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml"))
213
127
  response.stubs(:conditions).returns(nil)
214
128
  settings = OneLogin::RubySaml::Settings.new
@@ -218,52 +132,7 @@ class ResponseTest < Minitest::Test
218
132
  assert response.validate!
219
133
  end
220
134
 
221
- it "support signature elements with no KeyInfo if cert provided" do
222
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
223
- response.stubs(:conditions).returns(nil)
224
- settings = OneLogin::RubySaml::Settings.new
225
- response.settings = settings
226
- settings.idp_cert = ruby_saml_cert
227
- settings.idp_cert_fingerprint = nil
228
- XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
229
- assert response.validate!
230
- end
231
-
232
- it "support signature elements with no KeyInfo if cert provided as text" do
233
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
234
- response.stubs(:conditions).returns(nil)
235
- settings = OneLogin::RubySaml::Settings.new
236
- response.settings = settings
237
- settings.idp_cert = ruby_saml_cert_text
238
- settings.idp_cert_fingerprint = nil
239
- XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
240
- assert response.validate!
241
- end
242
-
243
- it "returns an error if the signature contains no KeyInfo, cert is not provided and soft" do
244
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
245
- response.stubs(:conditions).returns(nil)
246
- settings = OneLogin::RubySaml::Settings.new
247
- response.settings = settings
248
- settings.idp_cert = nil
249
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
250
- assert !response.is_valid?
251
- end
252
-
253
- it "raises an exception if the signature contains no KeyInfo, cert is not provided and no soft" do
254
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate)
255
- response.stubs(:conditions).returns(nil)
256
- settings = OneLogin::RubySaml::Settings.new
257
- response.settings = settings
258
- settings.idp_cert = nil
259
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
260
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
261
- response.validate!
262
- end
263
- assert_equal "Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", err.message
264
- end
265
-
266
- it "validate ADFS assertions" do
135
+ should "validate ADFS assertions" do
267
136
  response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256))
268
137
  response.stubs(:conditions).returns(nil)
269
138
  settings = OneLogin::RubySaml::Settings.new
@@ -272,7 +141,7 @@ class ResponseTest < Minitest::Test
272
141
  assert response.validate!
273
142
  end
274
143
 
275
- it "validate the digest" do
144
+ should "validate the digest" do
276
145
  response = OneLogin::RubySaml::Response.new(r1_response_document_6)
277
146
  response.stubs(:conditions).returns(nil)
278
147
  settings = OneLogin::RubySaml::Settings.new
@@ -281,75 +150,19 @@ class ResponseTest < Minitest::Test
281
150
  assert response.validate!
282
151
  end
283
152
 
284
- it "Prevent node text with comment (VU#475445) attack" do
285
- response_doc = File.read(File.join(File.dirname(__FILE__), "responses", 'response_node_text_attack.xml.base64'))
286
- response = OneLogin::RubySaml::Response.new(response_doc)
287
-
288
- assert_equal "support@onelogin.com", response.name_id
289
- assert_equal "smith", response.attributes["surname"]
290
- end
291
-
292
- describe '#validate_audience' do
293
- it "return true when sp_entity_id not set or empty" do
294
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
295
- response.stubs(:conditions).returns(nil)
296
- settings = OneLogin::RubySaml::Settings.new
297
- response.settings = settings
298
- settings.idp_cert_fingerprint = signature_fingerprint_valid_res
299
- assert response.is_valid?
300
- settings.sp_entity_id = ''
301
- assert response.is_valid?
302
- end
303
-
304
- it "return false when sp_entity_id set to incorrectly" do
305
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
306
- response.stubs(:conditions).returns(nil)
307
- settings = OneLogin::RubySaml::Settings.new
308
- response.settings = settings
309
- settings.idp_cert_fingerprint = signature_fingerprint_valid_res
310
- settings.sp_entity_id = 'wrong_audience'
311
- assert !response.is_valid?
312
- end
313
-
314
- it "return true when sp_entity_id set to correctly" do
315
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
316
- response.stubs(:conditions).returns(nil)
317
- settings = OneLogin::RubySaml::Settings.new
318
- response.settings = settings
319
- settings.idp_cert_fingerprint = signature_fingerprint_valid_res
320
- settings.sp_entity_id = 'https://someone.example.com/audience'
321
- assert response.is_valid?
322
- end
323
- end
324
- end
325
-
326
- describe "#validate_issuer" do
327
- it "return true when the issuer of the Message/Assertion matches the IdP entityId" do
328
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
329
- response.settings = settings
330
- assert response.send(:validate_issuer)
331
-
332
- response.settings.idp_entity_id = 'https://app.onelogin.com/saml2'
333
- assert response.send(:validate_issuer)
334
- end
335
-
336
- it "return false when the issuer of the Message does not match the IdP entityId" do
337
- response = OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_message.xml.base64"))
338
- response.settings = settings
339
- response.settings.idp_entity_id = 'http://idp.example.com/'
340
- assert !response.send(:validate_issuer)
341
- end
342
-
343
- it "return false when the issuer of the Assertion does not match the IdP entityId" do
344
- response = OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_assertion.xml.base64"))
153
+ should "validate SAML 2.0 XML structure" do
154
+ resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test')
155
+ response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml))
156
+ response.stubs(:conditions).returns(nil)
157
+ settings = OneLogin::RubySaml::Settings.new
158
+ settings.idp_cert_fingerprint = signature_fingerprint_1
345
159
  response.settings = settings
346
- response.settings.idp_entity_id = 'http://idp.example.com/'
347
- assert !response.send(:validate_issuer)
160
+ assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch'){ response.validate! }
348
161
  end
349
162
  end
350
163
 
351
- describe "#name_id" do
352
- it "extract the value of the name id element" do
164
+ context "#name_id" do
165
+ should "extract the value of the name id element" do
353
166
  response = OneLogin::RubySaml::Response.new(response_document)
354
167
  assert_equal "support@onelogin.com", response.name_id
355
168
 
@@ -357,19 +170,19 @@ class ResponseTest < Minitest::Test
357
170
  assert_equal "someone@example.com", response.name_id
358
171
  end
359
172
 
360
- it "be extractable from an OpenSAML response" do
173
+ should "be extractable from an OpenSAML response" do
361
174
  response = OneLogin::RubySaml::Response.new(fixture(:open_saml))
362
175
  assert_equal "someone@example.org", response.name_id
363
176
  end
364
177
 
365
- it "be extractable from a Simple SAML PHP response" do
178
+ should "be extractable from a Simple SAML PHP response" do
366
179
  response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php))
367
180
  assert_equal "someone@example.com", response.name_id
368
181
  end
369
182
  end
370
183
 
371
- describe "#check_conditions" do
372
- it "check time conditions" do
184
+ context "#check_conditions" do
185
+ should "check time conditions" do
373
186
  response = OneLogin::RubySaml::Response.new(response_document)
374
187
  assert !response.send(:validate_conditions, true)
375
188
  response = OneLogin::RubySaml::Response.new(response_document_6)
@@ -380,290 +193,187 @@ class ResponseTest < Minitest::Test
380
193
  assert response.send(:validate_conditions, true)
381
194
  end
382
195
 
383
- it "optionally allow for clock drift" do
196
+ should "optionally allow for clock drift" do
384
197
  # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
385
- expected_time = Time.parse("2011-06-14T18:21:01Z")
386
- Time.stubs(:now).returns(expected_time)
198
+ Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
387
199
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.515)
388
200
  assert !response.send(:validate_conditions, true)
389
201
 
390
- expected_time = Time.parse("2011-06-14T18:21:01Z")
391
- Time.stubs(:now).returns(expected_time)
202
+ Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
392
203
  response = OneLogin::RubySaml::Response.new(response_document_5, :allowed_clock_drift => 0.516)
393
204
  assert response.send(:validate_conditions, true)
394
205
  end
395
206
  end
396
207
 
397
- describe "validate_signature" do
398
- it "raises an exception when no cert or fingerprint provided" do
399
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
400
- settings = OneLogin::RubySaml::Settings.new
401
- response.settings = settings
402
- settings.idp_cert = nil
403
- settings.idp_cert_fingerprint = nil
404
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
405
- response.send(:validate_signature, false)
406
- end
407
- assert_equal "No fingerprint or certificate on settings", err.message
208
+ context "#attributes" do
209
+ should "extract the first attribute in a hash accessed via its symbol" do
210
+ response = OneLogin::RubySaml::Response.new(response_document)
211
+ assert_equal "demo", response.attributes[:uid]
408
212
  end
409
213
 
410
- it "raises an exception when wrong cert provided" do
411
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
412
- settings = OneLogin::RubySaml::Settings.new
413
- response.settings = settings
414
- settings.idp_cert = ruby_saml_cert2
415
- settings.idp_cert_fingerprint = nil
416
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
417
- response.send(:validate_signature, false)
418
- end
419
- assert_equal "Fingerprint mismatch", err.message
214
+ should "extract the first attribute in a hash accessed via its name" do
215
+ response = OneLogin::RubySaml::Response.new(response_document)
216
+ assert_equal "demo", response.attributes["uid"]
420
217
  end
421
218
 
422
- it "raises an exception when wrong fingerprint provided" do
423
- response = OneLogin::RubySaml::Response.new(response_document_valid_signed)
424
- settings = OneLogin::RubySaml::Settings.new
425
- response.settings = settings
426
- settings.idp_cert = nil
427
- settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
428
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
429
- response.send(:validate_signature, false)
430
- end
431
- assert_equal "Fingerprint mismatch", err.message
219
+ should "extract all attributes" do
220
+ response = OneLogin::RubySaml::Response.new(response_document)
221
+ assert_equal "demo", response.attributes[:uid]
222
+ assert_equal "value", response.attributes[:another_value]
432
223
  end
433
224
 
434
- it "raises an exception when no signature" do
435
- response_no_signed_elements = OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64"))
436
- settings.idp_cert_fingerprint = signature_fingerprint_1
437
- response_no_signed_elements.settings = settings
438
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
439
- response_no_signed_elements.validate!
440
- end
441
- assert_equal "Found an unexpected number of Signature Element. SAML Response rejected", err.message
225
+ should "work for implicit namespaces" do
226
+ response = OneLogin::RubySaml::Response.new(response_document_3)
227
+ assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
442
228
  end
443
- end
444
229
 
445
- describe "#attributes" do
446
- before do
447
- @response = OneLogin::RubySaml::Response.new(response_document)
230
+ should "not raise errors about nil/empty attributes for EncryptedAttributes" do
231
+ response = OneLogin::RubySaml::Response.new(response_document_7)
232
+ assert_equal 'Demo', response.attributes["first_name"]
448
233
  end
449
234
 
450
- it "extract the first attribute in a hash accessed via its symbol" do
451
- assert_equal "demo", @response.attributes[:uid]
235
+ should "not raise on responses without attributes" do
236
+ response = OneLogin::RubySaml::Response.new(response_document_4)
237
+ assert_equal OneLogin::RubySaml::Attributes.new, response.attributes
452
238
  end
453
239
 
454
- it "extract the first attribute in a hash accessed via its name" do
455
- assert_equal "demo", @response.attributes["uid"]
456
- end
240
+ context "#multiple values" do
241
+ should "extract single value as string" do
242
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
243
+ assert_equal "demo", response.attributes[:uid]
244
+ end
457
245
 
458
- it "extract all attributes" do
459
- assert_equal "demo", @response.attributes[:uid]
460
- assert_equal "value", @response.attributes[:another_value]
461
- end
246
+ should "extract single value as string in compatibility mode off" do
247
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
248
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
249
+ assert_equal ["demo"], response.attributes[:uid]
250
+ # classes are not reloaded between tests so restore default
251
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
252
+ end
462
253
 
463
- it "work for implicit namespaces" do
464
- response_3 = OneLogin::RubySaml::Response.new(response_document_3)
465
- assert_equal "someone@example.com", response_3.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
466
- end
254
+ should "extract first of multiple values as string for b/w compatibility" do
255
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
256
+ assert_equal 'value1', response.attributes[:another_value]
257
+ end
467
258
 
468
- it "not raise on responses without attributes" do
469
- response_4 = OneLogin::RubySaml::Response.new(response_document_4)
470
- assert_equal OneLogin::RubySaml::Attributes.new, response_4.attributes
471
- end
259
+ should "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
260
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
261
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
262
+ assert_equal ['value1', 'value2'], response.attributes[:another_value]
263
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
264
+ end
472
265
 
473
- it "extract attributes from all AttributeStatement tags" do
474
- assert_equal "smith", response_with_multiple_attribute_statements.attributes[:surname]
475
- assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname]
476
- end
266
+ should "return array with all attributes when asked in XML order" do
267
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
268
+ assert_equal ['value1', 'value2'], response.attributes.multi(:another_value)
269
+ end
477
270
 
478
- it "be manipulable by hash methods such as #merge and not raise an exception" do
479
- @response.attributes.merge({ :testing_attribute => "test" })
480
- end
271
+ should "return array with all attributes when asked in XML order in compatibility mode off" do
272
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
273
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
274
+ assert_equal ['value1', 'value2'], response.attributes.multi(:another_value)
275
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
276
+ end
481
277
 
482
- it "be manipulable by hash methods such as #shift and not raise an exception" do
483
- @response.attributes.shift
484
- end
278
+ should "return first of multiple values when multiple Attribute tags in XML" do
279
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
280
+ assert_equal 'role1', response.attributes[:role]
281
+ end
485
282
 
486
- it "be manipulable by hash methods such as #merge! and actually contain the value" do
487
- @response.attributes.merge!({ :testing_attribute => "test" })
488
- assert @response.attributes[:testing_attribute]
489
- end
283
+ should "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
284
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
285
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
286
+ assert_equal ['role1', 'role2', 'role3'], response.attributes[:role]
287
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
288
+ end
289
+
290
+ should "return all of multiple values in reverse order when multiple Attribute tags in XML" do
291
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
292
+ assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role)
293
+ end
294
+
295
+ should "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
296
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
297
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
298
+ assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role)
299
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
300
+ end
301
+
302
+ should "return nil value correctly" do
303
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
304
+ assert_nil response.attributes[:attribute_with_nil_value]
305
+ end
306
+
307
+ should "return nil value correctly when not in compatibility mode off" do
308
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
309
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
310
+ assert_equal [nil], response.attributes[:attribute_with_nil_value]
311
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
312
+ end
313
+
314
+ should "return multiple values including nil and empty string" do
315
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
316
+ assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings)
317
+ end
318
+
319
+ should "return multiple values from [] when not in compatibility mode off" do
320
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
321
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
322
+ assert_equal ["", "valuePresent", nil, nil], response.attributes[:attribute_with_nils_and_empty_strings]
323
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
324
+ end
325
+
326
+ should "check what happens when trying retrieve attribute that does not exists" do
327
+ response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
328
+ assert_equal nil, response.attributes[:attribute_not_exists]
329
+ assert_equal nil, response.attributes.single(:attribute_not_exists)
330
+ assert_equal nil, response.attributes.multi(:attribute_not_exists)
331
+
332
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
333
+ assert_equal nil, response.attributes[:attribute_not_exists]
334
+ assert_equal nil, response.attributes.single(:attribute_not_exists)
335
+ assert_equal nil, response.attributes.multi(:attribute_not_exists)
336
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
337
+ end
490
338
 
491
- it "be manipulable by hash methods such as #shift and actually remove the value" do
492
- removed_value = @response.attributes.shift
493
- assert_nil @response.attributes[removed_value[0]]
494
339
  end
495
340
  end
496
341
 
497
- describe "#session_expires_at" do
498
- it "extract the value of the SessionNotOnOrAfter attribute" do
342
+ context "#session_expires_at" do
343
+ should "extract the value of the SessionNotOnOrAfter attribute" do
499
344
  response = OneLogin::RubySaml::Response.new(response_document)
500
345
  assert response.session_expires_at.is_a?(Time)
501
346
 
502
347
  response = OneLogin::RubySaml::Response.new(response_document_2)
503
- assert response.session_expires_at.nil?
348
+ assert_nil response.session_expires_at
504
349
  end
505
350
  end
506
351
 
507
- describe "#issuer" do
508
- it "return the issuer inside the response assertion" do
352
+ context "#issuer" do
353
+ should "return the issuer inside the response assertion" do
509
354
  response = OneLogin::RubySaml::Response.new(response_document)
510
355
  assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer
511
356
  end
512
357
 
513
- it "return the issuer inside the response" do
358
+ should "return the issuer inside the response" do
514
359
  response = OneLogin::RubySaml::Response.new(response_document_2)
515
360
  assert_equal "wibble", response.issuer
516
361
  end
517
362
  end
518
363
 
519
- describe "#success" do
520
- it "find a status code that says success" do
364
+ context "#success" do
365
+ should "find a status code that says success" do
521
366
  response = OneLogin::RubySaml::Response.new(response_document)
522
- assert response.send(:success?)
367
+ response.success?
523
368
  end
524
369
  end
525
370
 
526
- describe '#xpath_first_from_signed_assertion' do
527
- it 'not allow arbitrary code execution' do
371
+ context '#xpath_first_from_signed_assertion' do
372
+ should 'not allow arbitrary code execution' do
528
373
  malicious_response_document = fixture('response_eval', false)
529
374
  response = OneLogin::RubySaml::Response.new(malicious_response_document)
530
375
  response.send(:xpath_first_from_signed_assertion)
531
- assert_nil $evalled
532
- end
533
- end
534
-
535
- describe "#multiple values" do
536
- it "extract single value as string" do
537
- assert_equal "demo", response_multiple_attr_values.attributes[:uid]
538
- end
539
-
540
- it "extract single value as string in compatibility mode off" do
541
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
542
- assert_equal ["demo"], response_multiple_attr_values.attributes[:uid]
543
- # classes are not reloaded between tests so restore default
544
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
545
- end
546
-
547
- it "extract first of multiple values as string for b/w compatibility" do
548
- assert_equal 'value1', response_multiple_attr_values.attributes[:another_value]
549
- end
550
-
551
- it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do
552
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
553
- assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value]
554
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
555
- end
556
-
557
- it "return array with all attributes when asked in XML order" do
558
- assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
559
- end
560
-
561
- it "return array with all attributes when asked in XML order in compatibility mode off" do
562
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
563
- assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value)
564
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
565
- end
566
-
567
- it "return first of multiple values when multiple Attribute tags in XML" do
568
- assert_equal 'role1', response_multiple_attr_values.attributes[:role]
569
- end
570
-
571
- it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do
572
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
573
- assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role]
574
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
575
- end
576
-
577
- it "return all of multiple values in reverse order when multiple Attribute tags in XML" do
578
- assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
579
- end
580
-
581
- it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do
582
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
583
- assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role)
584
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
585
- end
586
-
587
- it "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do
588
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
589
- assert_equal ['role1', 'role2', 'role3'], response_with_multiple_attribute_statements.attributes.multi(:role)
590
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
591
- end
592
-
593
- it "return nil value correctly" do
594
- assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value]
595
- end
596
-
597
- it "return nil value correctly when not in compatibility mode off" do
598
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
599
- assert [nil] == response_multiple_attr_values.attributes[:attribute_with_nil_value]
600
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
601
- end
602
-
603
- it "return multiple values including nil and empty string" do
604
- response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
605
- assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings)
606
- end
607
-
608
- it "return multiple values from [] when not in compatibility mode off" do
609
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
610
- assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings]
611
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
612
- end
613
-
614
- it "check what happens when trying retrieve attribute that does not exists" do
615
- assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
616
- assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
617
- assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
618
-
619
- OneLogin::RubySaml::Attributes.single_value_compatibility = false
620
- assert_nil response_multiple_attr_values.attributes[:attribute_not_exists]
621
- assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists)
622
- assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists)
623
- OneLogin::RubySaml::Attributes.single_value_compatibility = true
624
- end
625
- end
626
-
627
- describe "signature wrapping attack with encrypted assertion" do
628
- it "should not be valid" do
629
- settings = OneLogin::RubySaml::Settings.new
630
- settings.private_key = valid_key
631
- signature_wrapping_attack = read_response("encrypted_new_attack.xml.base64")
632
- response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
633
- response_wrapped.stubs(:conditions).returns(nil)
634
- response_wrapped.stubs(:validate_subject_confirmation).returns(true)
635
- settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c"
636
- assert !response_wrapped.is_valid?
637
- err = assert_raises(OneLogin::RubySaml::ValidationError) do
638
- response_wrapped.validate!
639
- end
640
- assert_equal "Found an invalid Signed Element. SAML Response rejected", err.message
641
- end
642
- end
643
-
644
- describe "signature wrapping attack - concealed SAML response body" do
645
- it "should not be valid" do
646
- settings = OneLogin::RubySaml::Settings.new
647
- signature_wrapping_attack = read_response("response_with_concealed_signed_assertion.xml")
648
- response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
649
- settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
650
- response_wrapped.stubs(:conditions).returns(nil)
651
- response_wrapped.stubs(:validate_subject_confirmation).returns(true)
652
- response_wrapped.stubs(:validate_structure).returns(true)
653
- assert !response_wrapped.is_valid?
654
- assert !response_wrapped.validate!
655
- end
656
- end
657
-
658
- describe "signature wrapping attack - doubled signed assertion SAML response" do
659
- it "should not be valid" do
660
- settings = OneLogin::RubySaml::Settings.new
661
- signature_wrapping_attack = read_response("response_with_doubled_signed_assertion.xml")
662
- response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
663
- settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
664
- response_wrapped.stubs(:conditions).returns(nil)
665
- response_wrapped.stubs(:validate_subject_confirmation).returns(true)
666
- assert !response_wrapped.is_valid?
376
+ assert_equal($evalled, nil)
667
377
  end
668
378
  end
669
379
  end