ruby-saml 0.8.16 → 0.9

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.

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