ruby-saml 0.9.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +71 -15
  5. data/changelog.md +15 -6
  6. data/lib/onelogin/ruby-saml.rb +1 -0
  7. data/lib/onelogin/ruby-saml/attribute_service.rb +25 -2
  8. data/lib/onelogin/ruby-saml/attributes.rb +42 -23
  9. data/lib/onelogin/ruby-saml/authrequest.rb +33 -8
  10. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +65 -10
  12. data/lib/onelogin/ruby-saml/logging.rb +14 -10
  13. data/lib/onelogin/ruby-saml/logoutrequest.rb +39 -14
  14. data/lib/onelogin/ruby-saml/logoutresponse.rb +166 -39
  15. data/lib/onelogin/ruby-saml/metadata.rb +40 -23
  16. data/lib/onelogin/ruby-saml/response.rb +562 -88
  17. data/lib/onelogin/ruby-saml/saml_message.rb +80 -14
  18. data/lib/onelogin/ruby-saml/settings.rb +62 -23
  19. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +210 -20
  20. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -13
  21. data/lib/onelogin/ruby-saml/utils.rb +163 -40
  22. data/lib/onelogin/ruby-saml/version.rb +1 -1
  23. data/lib/schemas/saml-schema-metadata-2.0.xsd +0 -2
  24. data/lib/xml_security.rb +87 -29
  25. data/ruby-saml.gemspec +1 -0
  26. data/test/certificates/{r1_certificate2_base64 → certificate_without_head_foot} +0 -0
  27. data/test/certificates/formatted_certificate +14 -0
  28. data/test/certificates/formatted_private_key +12 -0
  29. data/test/certificates/formatted_rsa_private_key +12 -0
  30. data/test/certificates/invalid_certificate1 +1 -0
  31. data/test/certificates/invalid_certificate2 +1 -0
  32. data/test/certificates/invalid_certificate3 +12 -0
  33. data/test/certificates/invalid_private_key1 +1 -0
  34. data/test/certificates/invalid_private_key2 +1 -0
  35. data/test/certificates/invalid_private_key3 +10 -0
  36. data/test/certificates/invalid_rsa_private_key1 +1 -0
  37. data/test/certificates/invalid_rsa_private_key2 +1 -0
  38. data/test/certificates/invalid_rsa_private_key3 +10 -0
  39. data/test/idp_metadata_parser_test.rb +41 -4
  40. data/test/logging_test.rb +62 -0
  41. data/test/logout_requests/invalid_slo_request.xml +6 -0
  42. data/test/{responses → logout_requests}/slo_request.xml +0 -0
  43. data/test/logout_requests/slo_request.xml.base64 +1 -0
  44. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  45. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  46. data/test/{responses → logout_responses}/logoutresponse_fixtures.rb +6 -6
  47. data/test/logoutrequest_test.rb +79 -52
  48. data/test/logoutresponse_test.rb +206 -59
  49. data/test/metadata_test.rb +77 -7
  50. data/test/request_test.rb +80 -65
  51. data/test/response_test.rb +862 -189
  52. data/test/responses/attackxee.xml +13 -0
  53. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  54. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  55. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  56. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  57. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  58. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  59. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  60. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  61. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  62. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  63. data/test/responses/invalids/no_id.xml.base64 +1 -0
  64. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  65. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  66. data/test/responses/invalids/no_status.xml.base64 +1 -0
  67. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  68. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  69. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  70. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  71. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  72. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  73. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  74. data/test/responses/{response4.xml.base64 → response_assertion_wrapped.xml.base64} +0 -0
  75. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  76. data/test/responses/response_unsigned_xml_base64 +1 -0
  77. data/test/responses/{response5.xml.base64 → response_with_saml2_namespace.xml.base64} +0 -0
  78. data/test/responses/{response3.xml.base64 → response_with_signed_assertion.xml.base64} +0 -0
  79. data/test/responses/{r1_response6.xml.base64 → response_with_signed_assertion_2.xml.base64} +0 -0
  80. data/test/responses/{response1.xml.base64 → response_with_undefined_recipient.xml.base64} +0 -0
  81. data/test/responses/{response2.xml.base64 → response_without_attributes.xml.base64} +0 -0
  82. data/test/responses/{wrapped_response_2.xml.base64 → response_wrapped.xml.base64} +0 -0
  83. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  84. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  85. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  86. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  87. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  88. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  89. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  90. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  91. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  92. data/test/responses/valid_response.xml.base64 +1 -0
  93. data/test/saml_message_test.rb +56 -0
  94. data/test/settings_test.rb +138 -1
  95. data/test/slo_logoutrequest_test.rb +239 -28
  96. data/test/slo_logoutresponse_test.rb +93 -71
  97. data/test/test_helper.rb +138 -31
  98. data/test/utils_test.rb +129 -25
  99. data/test/xml_security_test.rb +140 -71
  100. metadata +142 -25
  101. data/test/responses/response_node_text_attack.xml.base64 +0 -1
@@ -6,125 +6,109 @@ class SloLogoutresponseTest < Minitest::Test
6
6
 
7
7
  describe "SloLogoutresponse" do
8
8
  let(:settings) { OneLogin::RubySaml::Settings.new }
9
+ let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) }
9
10
 
10
- it "create the deflated SAMLResponse URL parameter" do
11
+ before do
12
+ settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT'
11
13
  settings.idp_slo_target_url = "http://unauth.com/logout"
12
14
  settings.name_identifier_value = "f00f00"
13
15
  settings.compress_request = true
16
+ settings.certificate = ruby_saml_cert_text
17
+ settings.private_key = ruby_saml_key_text
18
+ logout_request.settings = settings
19
+ end
14
20
 
15
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
16
-
17
- assert request.is_valid?
18
-
19
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id)
20
- assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLResponse=/
21
+ it "create the deflated SAMLResponse URL parameter" do
22
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id)
23
+ assert_match /^http:\/\/unauth\.com\/logout\?SAMLResponse=/, unauth_url
21
24
 
22
25
  inflated = decode_saml_response_payload(unauth_url)
23
-
24
26
  assert_match /^<samlp:LogoutResponse/, inflated
25
27
  end
26
28
 
27
29
  it "support additional params" do
28
- settings.idp_slo_target_url = "http://unauth.com/logout"
29
- settings.name_identifier_value = "f00f00"
30
- settings.compress_request = true
30
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :hello => nil })
31
+ assert_match /&hello=$/, unauth_url
31
32
 
32
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
33
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :foo => "bar" })
34
+ assert_match /&foo=bar$/, unauth_url
33
35
 
34
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :hello => nil })
35
- assert unauth_url =~ /&hello=$/
36
-
37
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :foo => "bar" })
38
- assert unauth_url =~ /&foo=bar$/
39
-
40
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :RelayState => "http://idp.example.com" })
41
- assert unauth_url =~ /&RelayState=http%3A%2F%2Fidp.example.com$/
36
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => "http://idp.example.com" })
37
+ assert_match /&RelayState=http%3A%2F%2Fidp.example.com$/, unauth_url
42
38
  end
43
39
 
44
40
  it "set InResponseTo to the ID from the logout request" do
45
- settings.idp_slo_target_url = "http://unauth.com/logout"
46
- settings.name_identifier_value = "f00f00"
47
- settings.compress_request = true
48
-
49
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
50
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id)
41
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id)
51
42
 
52
43
  inflated = decode_saml_response_payload(unauth_url)
53
-
54
44
  assert_match /InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated
55
45
  end
56
46
 
57
47
  it "set a custom successful logout message on the response" do
58
- settings.idp_slo_target_url = "http://unauth.com/logout"
59
- settings.name_identifier_value = "f00f00"
60
- settings.compress_request = true
61
-
62
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
63
- unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, "Custom Logout Message")
48
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, "Custom Logout Message")
64
49
 
65
50
  inflated = decode_saml_response_payload(unauth_url)
66
-
67
51
  assert_match /<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated
68
52
  end
69
53
 
70
54
  describe "when the settings indicate to sign (embedded) logout response" do
71
- it "create a signed logout response" do
72
- settings = OneLogin::RubySaml::Settings.new
55
+
56
+ before do
73
57
  settings.compress_response = false
74
- settings.idp_slo_target_url = "http://example.com?field=value"
75
58
  settings.security[:logout_responses_signed] = true
76
59
  settings.security[:embed_sign] = true
77
- settings.certificate = ruby_saml_cert_text
78
- settings.private_key = ruby_saml_key_text
60
+ end
79
61
 
80
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
81
- params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, request.id, "Custom Logout Message")
62
+ it "create a signed logout response" do
63
+ logout_request.settings = settings
64
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
82
65
 
83
66
  response_xml = Base64.decode64(params["SAMLResponse"])
84
67
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
85
- response_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/
86
- response_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#sha1'\/>/
68
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/, response_xml
69
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#sha1'\/>/, response_xml
87
70
  end
88
71
 
89
72
  it "create a signed logout response with 256 digest and signature methods" do
90
- settings = OneLogin::RubySaml::Settings.new
91
- settings.compress_response = false
92
- settings.idp_slo_target_url = "http://example.com?field=value"
93
- settings.security[:logout_responses_signed] = true
94
- settings.security[:embed_sign] = true
95
73
  settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
74
+ settings.security[:digest_method] = XMLSecurity::Document::SHA256
75
+
76
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
77
+
78
+ response_xml = Base64.decode64(params["SAMLResponse"])
79
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
80
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/, response_xml
81
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#sha256'\/>/, response_xml
82
+ end
83
+
84
+ it "create a signed logout response with 512 digest and signature method RSA_SHA384" do
85
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
96
86
  settings.security[:digest_method] = XMLSecurity::Document::SHA512
97
- settings.certificate = ruby_saml_cert_text
98
- settings.private_key = ruby_saml_key_text
87
+ logout_request.settings = settings
99
88
 
100
- request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
101
- params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, request.id, "Custom Logout Message")
89
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
102
90
 
103
91
  response_xml = Base64.decode64(params["SAMLResponse"])
104
92
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
105
- response_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/
106
- response_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha512'\/>/
93
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha384'\/>/, response_xml
94
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#sha512'\/>/, response_xml
107
95
  end
108
96
  end
109
97
 
110
98
  describe "#create_params when the settings indicate to sign the logout response" do
111
- def setup
112
- @settings = OneLogin::RubySaml::Settings.new
113
- @settings.compress_response = false
114
- @settings.idp_slo_target_url = "http://example.com?field=value"
115
- @settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
116
- @settings.security[:logout_responses_signed] = true
117
- @settings.security[:embed_sign] = false
118
- @settings.certificate = ruby_saml_cert_text
119
- @settings.private_key = ruby_saml_key_text
120
- @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
121
- @request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
99
+
100
+ let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
101
+
102
+ before do
103
+ settings.compress_response = false
104
+ settings.security[:logout_responses_signed] = true
105
+ settings.security[:embed_sign] = false
122
106
  end
123
107
 
124
108
  it "create a signature parameter with RSA_SHA1 and validate it" do
125
109
  settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
126
110
 
127
- params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(@settings, @request.id, "Custom Logout Message", :RelayState => 'http://example.com')
111
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
128
112
  assert params['SAMLResponse']
129
113
  assert params[:RelayState]
130
114
  assert params['Signature']
@@ -136,13 +120,13 @@ class SloLogoutresponseTest < Minitest::Test
136
120
 
137
121
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
138
122
  assert_equal signature_algorithm, OpenSSL::Digest::SHA1
139
- assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
123
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
140
124
  end
141
125
 
142
- it "create a signature parameter with RSA_SHA256 and validate it" do
143
- @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
126
+ it "create a signature parameter with RSA_SHA256 /SHA256 and validate it" do
127
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
144
128
 
145
- params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(@settings, @request.id, "Custom Logout Message", :RelayState => 'http://example.com')
129
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
146
130
  assert params['SAMLResponse']
147
131
  assert params[:RelayState]
148
132
  assert params['Signature']
@@ -155,7 +139,45 @@ class SloLogoutresponseTest < Minitest::Test
155
139
 
156
140
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
157
141
  assert_equal signature_algorithm, OpenSSL::Digest::SHA256
158
- assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
142
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
143
+ end
144
+
145
+ it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
146
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
147
+
148
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
149
+ assert params['SAMLResponse']
150
+ assert params[:RelayState]
151
+ assert params['Signature']
152
+
153
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384
154
+
155
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
156
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
157
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
158
+
159
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
160
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA384
161
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
162
+ end
163
+
164
+ it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
165
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
166
+
167
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
168
+ assert params['SAMLResponse']
169
+ assert params[:RelayState]
170
+ assert params['Signature']
171
+
172
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
173
+
174
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
175
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
176
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
177
+
178
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
179
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA512
180
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
159
181
  end
160
182
 
161
183
  end
@@ -1,3 +1,11 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start do
4
+ add_filter "test/"
5
+ add_filter "lib/onelogin/ruby-saml/logging.rb"
6
+ end
7
+
8
+ require 'stringio'
1
9
  require 'rubygems'
2
10
  require 'bundler'
3
11
  require 'minitest/autorun'
@@ -8,7 +16,10 @@ Bundler.require :default, :test
8
16
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
17
  $LOAD_PATH.unshift(File.dirname(__FILE__))
10
18
 
11
- ENV["ruby-saml/testing"] = "1"
19
+ require 'onelogin/ruby-saml/logging'
20
+
21
+ TEST_LOGGER = Logger.new(StringIO.new)
22
+ OneLogin::RubySaml::Logging.logger = TEST_LOGGER
12
23
 
13
24
  class Minitest::Test
14
25
  def fixture(document, base64 = true)
@@ -20,74 +31,141 @@ class Minitest::Test
20
31
  end
21
32
  end
22
33
 
23
- def response_document
24
- @response_document ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response1.xml.base64'))
25
- end
26
-
27
- def response_document_2
28
- @response_document2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response2.xml.base64'))
34
+ def read_response(response)
35
+ File.read(File.join(File.dirname(__FILE__), "responses", response))
29
36
  end
30
37
 
31
- def response_document_3
32
- @response_document3 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response3.xml.base64'))
38
+ def read_invalid_response(response)
39
+ File.read(File.join(File.dirname(__FILE__), "responses", "invalids", response))
33
40
  end
34
41
 
35
- def response_document_4
36
- @response_document4 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response4.xml.base64'))
42
+ def read_logout_request(request)
43
+ File.read(File.join(File.dirname(__FILE__), "logout_requests", request))
37
44
  end
38
45
 
39
- def response_document_5
40
- @response_document5 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response5.xml.base64'))
46
+ def read_certificate(certificate)
47
+ File.read(File.join(File.dirname(__FILE__), "certificates", certificate))
41
48
  end
42
49
 
43
- def r1_response_document_6
44
- @response_document6 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'r1_response6.xml.base64'))
50
+ def response_document_valid_signed
51
+ @response_document_valid_signed ||= read_response("valid_response.xml.base64")
45
52
  end
46
53
 
47
- def ampersands_response
48
- @ampersands_response ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_with_ampersands.xml.base64'))
54
+ def response_document_without_recipient
55
+ @response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64")
49
56
  end
50
57
 
51
- def response_document_6
52
- doc = Base64.decode64(response_document)
58
+ def response_document_without_recipient_with_time_updated
59
+ doc = Base64.decode64(response_document_without_recipient)
53
60
  doc.gsub!(/NotBefore=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotBefore=\"#{(Time.now-300).getutc.strftime("%Y-%m-%dT%XZ")}\"")
54
61
  doc.gsub!(/NotOnOrAfter=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotOnOrAfter=\"#{(Time.now+300).getutc.strftime("%Y-%m-%dT%XZ")}\"")
55
62
  Base64.encode64(doc)
56
63
  end
57
64
 
58
- def response_document_7
59
- @response_document7 ||= Base64.encode64(File.read(File.join(File.dirname(__FILE__), 'responses', 'response_no_cert_and_encrypted_attrs.xml')))
65
+ def response_document_without_attributes
66
+ @response_document_without_attributes ||= read_response("response_without_attributes.xml.base64")
67
+ end
68
+
69
+ def response_document_with_signed_assertion
70
+ @response_document_with_signed_assertion ||= read_response("response_with_signed_assertion.xml.base64")
71
+ end
72
+
73
+ def response_document_with_signed_assertion_2
74
+ @response_document_with_signed_assertion_2 ||= read_response("response_with_signed_assertion_2.xml.base64")
75
+ end
76
+
77
+ def response_document_unsigned
78
+ @response_document_unsigned ||= read_response("response_unsigned_xml_base64")
79
+ end
80
+
81
+ def response_document_with_saml2_namespace
82
+ @response_document_with_saml2_namespace ||= read_response("response_with_saml2_namespace.xml.base64")
83
+ end
84
+
85
+ def ampersands_document
86
+ @ampersands_response ||= read_response("response_with_ampersands.xml.base64")
87
+ end
88
+
89
+ def response_document_no_cert_and_encrypted_attrs
90
+ @response_document_no_cert_and_encrypted_attrs ||= Base64.encode64(read_response("response_no_cert_and_encrypted_attrs.xml"))
91
+ end
92
+
93
+ def response_document_wrapped
94
+ @response_document_wrapped ||= read_response("response_wrapped.xml.base64")
95
+ end
96
+
97
+ def response_document_assertion_wrapped
98
+ @response_document_assertion_wrapped ||= read_response("response_assertion_wrapped.xml.base64")
99
+ end
100
+
101
+ def response_document_encrypted_nameid
102
+ @response_document_encrypted_nameid ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_nameid.xml.base64'))
103
+ end
104
+
105
+ def signed_message_encrypted_unsigned_assertion
106
+ @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64'))
107
+ end
108
+
109
+ def signed_message_encrypted_signed_assertion
110
+ @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64'))
111
+ end
112
+
113
+ def unsigned_message_encrypted_signed_assertion
114
+ @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64'))
60
115
  end
61
116
 
62
- def wrapped_response_2
63
- @wrapped_response_2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'wrapped_response_2.xml.base64'))
117
+ def unsigned_message_encrypted_unsigned_assertion
118
+ @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64'))
64
119
  end
65
120
 
66
121
  def signature_fingerprint_1
67
122
  @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
68
123
  end
69
124
 
70
- def signature_1
71
- @signature1 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'certificate1'))
125
+ # certificate used on response_with_undefined_recipient
126
+ def signature_1
127
+ @signature1 ||= read_certificate("certificate1")
72
128
  end
73
129
 
74
- def r1_signature_2
75
- @signature2 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'r1_certificate2_base64'))
130
+ # certificate used on response_document_with_signed_assertion_2
131
+ def certificate_without_head_foot
132
+ @certificate_without_head_foot ||= read_certificate("certificate_without_head_foot")
76
133
  end
77
134
 
78
135
  def idp_metadata
79
- @idp_metadata ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'idp_descriptor.xml'))
136
+ @idp_metadata ||= read_response("idp_descriptor.xml")
80
137
  end
81
138
 
82
139
  def logout_request_document
83
140
  unless @logout_request_document
84
- xml = File.read(File.join(File.dirname(__FILE__), 'responses', 'slo_request.xml'))
141
+ xml = read_logout_request("slo_request.xml")
85
142
  deflated = Zlib::Deflate.deflate(xml, 9)[2..-5]
86
143
  @logout_request_document = Base64.encode64(deflated)
87
144
  end
88
145
  @logout_request_document
89
146
  end
90
147
 
148
+ def logout_request_xml_with_session_index
149
+ @logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml'))
150
+ end
151
+
152
+ def invalid_logout_request_document
153
+ unless @invalid_logout_request_document
154
+ xml = File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'invalid_slo_request.xml'))
155
+ deflated = Zlib::Deflate.deflate(xml, 9)[2..-5]
156
+ @invalid_logout_request_document = Base64.encode64(deflated)
157
+ end
158
+ @invalid_logout_request_document
159
+ end
160
+
161
+ def logout_request_base64
162
+ @logout_request_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml.base64'))
163
+ end
164
+
165
+ def logout_request_deflated_base64
166
+ @logout_request_deflated_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_deflated.xml.base64'))
167
+ end
168
+
91
169
  def ruby_saml_cert
92
170
  @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
93
171
  end
@@ -97,7 +175,7 @@ class Minitest::Test
97
175
  end
98
176
 
99
177
  def ruby_saml_cert_text
100
- File.read(File.join(File.dirname(__FILE__), 'certificates', 'ruby-saml.crt'))
178
+ read_certificate("ruby-saml.crt")
101
179
  end
102
180
 
103
181
  def ruby_saml_key
@@ -105,7 +183,7 @@ class Minitest::Test
105
183
  end
106
184
 
107
185
  def ruby_saml_key_text
108
- File.read(File.join(File.dirname(__FILE__), 'certificates', 'ruby-saml.key'))
186
+ read_certificate("ruby-saml.key")
109
187
  end
110
188
 
111
189
  #
@@ -142,4 +220,33 @@ class Minitest::Test
142
220
  zstream.close
143
221
  inflated
144
222
  end
223
+
224
+ SCHEMA_DIR = File.expand_path(File.join(__FILE__, '../../lib/schemas'))
225
+
226
+ #
227
+ # validate an xml document against the given schema
228
+ #
229
+ def validate_xml!(document, schema)
230
+ Dir.chdir(SCHEMA_DIR) do
231
+ xsd = if schema.is_a? Nokogiri::XML::Schema
232
+ schema
233
+ else
234
+ Nokogiri::XML::Schema(File.read(schema))
235
+ end
236
+
237
+ xml = if document.is_a? Nokogiri::XML::Document
238
+ document
239
+ else
240
+ Nokogiri::XML(document) { |c| c.strict }
241
+ end
242
+
243
+ result = xsd.validate(xml)
244
+
245
+ if result.length != 0
246
+ raise "Schema validation failed! XSD validation errors: #{result.join(", ")}"
247
+ else
248
+ true
249
+ end
250
+ end
251
+ end
145
252
  end