r-saml 1.0.1

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 (140) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +19 -0
  7. data/README.md +584 -0
  8. data/Rakefile +27 -0
  9. data/changelog.md +75 -0
  10. data/gemfiles/nokogiri-1.5.gemfile +5 -0
  11. data/lib/onelogin/ruby-saml.rb +17 -0
  12. data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
  13. data/lib/onelogin/ruby-saml/attributes.rb +128 -0
  14. data/lib/onelogin/ruby-saml/authrequest.rb +165 -0
  15. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  16. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
  17. data/lib/onelogin/ruby-saml/logging.rb +30 -0
  18. data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
  19. data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
  20. data/lib/onelogin/ruby-saml/metadata.rb +123 -0
  21. data/lib/onelogin/ruby-saml/response.rb +735 -0
  22. data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
  23. data/lib/onelogin/ruby-saml/settings.rb +165 -0
  24. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
  25. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
  26. data/lib/onelogin/ruby-saml/utils.rb +172 -0
  27. data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
  28. data/lib/onelogin/ruby-saml/version.rb +5 -0
  29. data/lib/ruby-saml.rb +1 -0
  30. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  31. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  32. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  33. data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
  34. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  35. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  36. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  37. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  38. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  39. data/lib/schemas/xenc-schema.xsd +136 -0
  40. data/lib/schemas/xml.xsd +287 -0
  41. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  42. data/lib/xml_security.rb +368 -0
  43. data/r-saml.gemspec +64 -0
  44. data/test/certificates/certificate1 +12 -0
  45. data/test/certificates/certificate_without_head_foot +1 -0
  46. data/test/certificates/formatted_certificate +14 -0
  47. data/test/certificates/formatted_private_key +12 -0
  48. data/test/certificates/formatted_rsa_private_key +12 -0
  49. data/test/certificates/invalid_certificate1 +1 -0
  50. data/test/certificates/invalid_certificate2 +1 -0
  51. data/test/certificates/invalid_certificate3 +12 -0
  52. data/test/certificates/invalid_private_key1 +1 -0
  53. data/test/certificates/invalid_private_key2 +1 -0
  54. data/test/certificates/invalid_private_key3 +10 -0
  55. data/test/certificates/invalid_rsa_private_key1 +1 -0
  56. data/test/certificates/invalid_rsa_private_key2 +1 -0
  57. data/test/certificates/invalid_rsa_private_key3 +10 -0
  58. data/test/certificates/ruby-saml.crt +14 -0
  59. data/test/certificates/ruby-saml.key +15 -0
  60. data/test/idp_metadata_parser_test.rb +95 -0
  61. data/test/logging_test.rb +62 -0
  62. data/test/logout_requests/invalid_slo_request.xml +6 -0
  63. data/test/logout_requests/slo_request.xml +4 -0
  64. data/test/logout_requests/slo_request.xml.base64 +1 -0
  65. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  66. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  67. data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
  68. data/test/logoutrequest_test.rb +211 -0
  69. data/test/logoutresponse_test.rb +258 -0
  70. data/test/metadata_test.rb +203 -0
  71. data/test/request_test.rb +282 -0
  72. data/test/response_test.rb +1159 -0
  73. data/test/responses/adfs_response_sha1.xml +46 -0
  74. data/test/responses/adfs_response_sha256.xml +46 -0
  75. data/test/responses/adfs_response_sha384.xml +46 -0
  76. data/test/responses/adfs_response_sha512.xml +46 -0
  77. data/test/responses/adfs_response_xmlns.xml +45 -0
  78. data/test/responses/attackxee.xml +13 -0
  79. data/test/responses/idp_descriptor.xml +3 -0
  80. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  81. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  82. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  83. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  84. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  85. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  86. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  87. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  88. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  89. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  90. data/test/responses/invalids/no_id.xml.base64 +1 -0
  91. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  92. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  93. data/test/responses/invalids/no_status.xml.base64 +1 -0
  94. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  95. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  96. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  97. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  99. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  100. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  101. data/test/responses/no_signature_ns.xml +48 -0
  102. data/test/responses/open_saml_response.xml +56 -0
  103. data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
  104. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  105. data/test/responses/response_eval.xml +7 -0
  106. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  107. data/test/responses/response_unsigned_xml_base64 +1 -0
  108. data/test/responses/response_with_ampersands.xml +139 -0
  109. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  110. data/test/responses/response_with_multiple_attribute_values.xml +67 -0
  111. data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
  112. data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
  113. data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
  114. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  115. data/test/responses/response_without_attributes.xml.base64 +79 -0
  116. data/test/responses/response_without_reference_uri.xml.base64 +1 -0
  117. data/test/responses/response_wrapped.xml.base64 +150 -0
  118. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  119. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  120. data/test/responses/signed_nameid_in_atts.xml +47 -0
  121. data/test/responses/signed_unqual_nameid_in_atts.xml +47 -0
  122. data/test/responses/simple_saml_php.xml +71 -0
  123. data/test/responses/starfield_response.xml.base64 +1 -0
  124. data/test/responses/test_sign.xml +43 -0
  125. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  129. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  130. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  131. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  132. data/test/responses/valid_response.xml.base64 +1 -0
  133. data/test/saml_message_test.rb +56 -0
  134. data/test/settings_test.rb +218 -0
  135. data/test/slo_logoutrequest_test.rb +275 -0
  136. data/test/slo_logoutresponse_test.rb +185 -0
  137. data/test/test_helper.rb +257 -0
  138. data/test/utils_test.rb +145 -0
  139. data/test/xml_security_test.rb +328 -0
  140. metadata +421 -0
@@ -0,0 +1,185 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ require 'onelogin/ruby-saml/slo_logoutresponse'
4
+
5
+ class SloLogoutresponseTest < Minitest::Test
6
+
7
+ describe "SloLogoutresponse" do
8
+ let(:settings) { OneLogin::RubySaml::Settings.new }
9
+ let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) }
10
+
11
+ before do
12
+ settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT'
13
+ settings.idp_slo_target_url = "http://unauth.com/logout"
14
+ settings.name_identifier_value = "f00f00"
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
20
+
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
24
+
25
+ inflated = decode_saml_response_payload(unauth_url)
26
+ assert_match /^<samlp:LogoutResponse/, inflated
27
+ end
28
+
29
+ it "support additional params" do
30
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :hello => nil })
31
+ assert_match /&hello=$/, unauth_url
32
+
33
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :foo => "bar" })
34
+ assert_match /&foo=bar$/, unauth_url
35
+
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
38
+ end
39
+
40
+ it "set InResponseTo to the ID from the logout request" do
41
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id)
42
+
43
+ inflated = decode_saml_response_payload(unauth_url)
44
+ assert_match /InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated
45
+ end
46
+
47
+ it "set a custom successful logout message on the response" do
48
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, "Custom Logout Message")
49
+
50
+ inflated = decode_saml_response_payload(unauth_url)
51
+ assert_match /<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated
52
+ end
53
+
54
+ describe "when the settings indicate to sign (embedded) logout response" do
55
+
56
+ before do
57
+ settings.compress_response = false
58
+ settings.security[:logout_responses_signed] = true
59
+ settings.security[:embed_sign] = true
60
+ end
61
+
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")
65
+
66
+ response_xml = Base64.decode64(params["SAMLResponse"])
67
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
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
70
+ end
71
+
72
+ it "create a signed logout response with 256 digest and signature methods" do
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
86
+ settings.security[:digest_method] = XMLSecurity::Document::SHA512
87
+ logout_request.settings = settings
88
+
89
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
90
+
91
+ response_xml = Base64.decode64(params["SAMLResponse"])
92
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
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
95
+ end
96
+ end
97
+
98
+ describe "#create_params when the settings indicate to sign the logout response" do
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
106
+ end
107
+
108
+ it "create a signature parameter with RSA_SHA1 and validate it" do
109
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
110
+
111
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
112
+ assert params['SAMLResponse']
113
+ assert params[:RelayState]
114
+ assert params['Signature']
115
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
116
+
117
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
118
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
119
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
120
+
121
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
122
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA1
123
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
124
+ end
125
+
126
+ it "create a signature parameter with RSA_SHA256 /SHA256 and validate it" do
127
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
128
+
129
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com')
130
+ assert params['SAMLResponse']
131
+ assert params[:RelayState]
132
+ assert params['Signature']
133
+
134
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
135
+
136
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
137
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
138
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
139
+
140
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
141
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
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)
181
+ end
182
+
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,257 @@
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'
9
+ require 'rubygems'
10
+ require 'bundler'
11
+ require 'minitest/autorun'
12
+ require 'mocha/setup'
13
+ require 'timecop'
14
+
15
+ Bundler.require :default, :test
16
+
17
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
19
+
20
+ require 'onelogin/ruby-saml/logging'
21
+
22
+ TEST_LOGGER = Logger.new(StringIO.new)
23
+ OneLogin::RubySaml::Logging.logger = TEST_LOGGER
24
+
25
+ class Minitest::Test
26
+ def fixture(document, base64 = true)
27
+ response = Dir.glob(File.join(File.dirname(__FILE__), "responses", "#{document}*")).first
28
+ if base64 && response =~ /\.xml$/
29
+ Base64.encode64(File.read(response))
30
+ else
31
+ File.read(response)
32
+ end
33
+ end
34
+
35
+ def read_response(response)
36
+ File.read(File.join(File.dirname(__FILE__), "responses", response))
37
+ end
38
+
39
+ def read_invalid_response(response)
40
+ File.read(File.join(File.dirname(__FILE__), "responses", "invalids", response))
41
+ end
42
+
43
+ def read_logout_request(request)
44
+ File.read(File.join(File.dirname(__FILE__), "logout_requests", request))
45
+ end
46
+
47
+ def read_certificate(certificate)
48
+ File.read(File.join(File.dirname(__FILE__), "certificates", certificate))
49
+ end
50
+
51
+ def response_document_valid_signed
52
+ @response_document_valid_signed ||= read_response("valid_response.xml.base64")
53
+ end
54
+
55
+ def response_document_without_recipient
56
+ @response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64")
57
+ end
58
+
59
+ def response_document_without_recipient_with_time_updated
60
+ doc = Base64.decode64(response_document_without_recipient)
61
+ 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")}\"")
62
+ 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")}\"")
63
+ Base64.encode64(doc)
64
+ end
65
+
66
+ def response_document_without_attributes
67
+ @response_document_without_attributes ||= read_response("response_without_attributes.xml.base64")
68
+ end
69
+
70
+ def response_document_without_reference_uri
71
+ @response_document_without_reference_uri ||= read_response("response_without_reference_uri.xml.base64")
72
+ end
73
+
74
+ def response_document_with_signed_assertion
75
+ @response_document_with_signed_assertion ||= read_response("response_with_signed_assertion.xml.base64")
76
+ end
77
+
78
+ def response_document_with_signed_assertion_2
79
+ @response_document_with_signed_assertion_2 ||= read_response("response_with_signed_assertion_2.xml.base64")
80
+ end
81
+
82
+ def response_document_unsigned
83
+ @response_document_unsigned ||= read_response("response_unsigned_xml_base64")
84
+ end
85
+
86
+ def response_document_with_saml2_namespace
87
+ @response_document_with_saml2_namespace ||= read_response("response_with_saml2_namespace.xml.base64")
88
+ end
89
+
90
+ def ampersands_document
91
+ @ampersands_response ||= read_response("response_with_ampersands.xml.base64")
92
+ end
93
+
94
+ def response_document_no_cert_and_encrypted_attrs
95
+ @response_document_no_cert_and_encrypted_attrs ||= Base64.encode64(read_response("response_no_cert_and_encrypted_attrs.xml"))
96
+ end
97
+
98
+ def response_document_wrapped
99
+ @response_document_wrapped ||= read_response("response_wrapped.xml.base64")
100
+ end
101
+
102
+ def response_document_assertion_wrapped
103
+ @response_document_assertion_wrapped ||= read_response("response_assertion_wrapped.xml.base64")
104
+ end
105
+
106
+ def response_document_encrypted_nameid
107
+ @response_document_encrypted_nameid ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_nameid.xml.base64'))
108
+ end
109
+
110
+ def signed_message_encrypted_unsigned_assertion
111
+ @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64'))
112
+ end
113
+
114
+ def signed_message_encrypted_signed_assertion
115
+ @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64'))
116
+ end
117
+
118
+ def unsigned_message_encrypted_signed_assertion
119
+ @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64'))
120
+ end
121
+
122
+ def unsigned_message_encrypted_unsigned_assertion
123
+ @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64'))
124
+ end
125
+
126
+ def signature_fingerprint_1
127
+ @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
128
+ end
129
+
130
+ # certificate used on response_with_undefined_recipient
131
+ def signature_1
132
+ @signature1 ||= read_certificate("certificate1")
133
+ end
134
+
135
+ # certificate used on response_document_with_signed_assertion_2
136
+ def certificate_without_head_foot
137
+ @certificate_without_head_foot ||= read_certificate("certificate_without_head_foot")
138
+ end
139
+
140
+ def idp_metadata
141
+ @idp_metadata ||= read_response("idp_descriptor.xml")
142
+ end
143
+
144
+ def logout_request_document
145
+ unless @logout_request_document
146
+ xml = read_logout_request("slo_request.xml")
147
+ deflated = Zlib::Deflate.deflate(xml, 9)[2..-5]
148
+ @logout_request_document = Base64.encode64(deflated)
149
+ end
150
+ @logout_request_document
151
+ end
152
+
153
+ def logout_request_xml_with_session_index
154
+ @logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml'))
155
+ end
156
+
157
+ def invalid_logout_request_document
158
+ unless @invalid_logout_request_document
159
+ xml = File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'invalid_slo_request.xml'))
160
+ deflated = Zlib::Deflate.deflate(xml, 9)[2..-5]
161
+ @invalid_logout_request_document = Base64.encode64(deflated)
162
+ end
163
+ @invalid_logout_request_document
164
+ end
165
+
166
+ def logout_request_base64
167
+ @logout_request_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml.base64'))
168
+ end
169
+
170
+ def logout_request_deflated_base64
171
+ @logout_request_deflated_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_deflated.xml.base64'))
172
+ end
173
+
174
+ def ruby_saml_cert
175
+ @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
176
+ end
177
+
178
+ def ruby_saml_cert_fingerprint
179
+ @ruby_saml_cert_fingerprint ||= Digest::SHA1.hexdigest(ruby_saml_cert.to_der).scan(/../).join(":")
180
+ end
181
+
182
+ def ruby_saml_cert_text
183
+ read_certificate("ruby-saml.crt")
184
+ end
185
+
186
+ def ruby_saml_key
187
+ @ruby_saml_key ||= OpenSSL::PKey::RSA.new(ruby_saml_key_text)
188
+ end
189
+
190
+ def ruby_saml_key_text
191
+ read_certificate("ruby-saml.key")
192
+ end
193
+
194
+ #
195
+ # logoutresponse fixtures
196
+ #
197
+ def random_id
198
+ "_#{UUID.new.generate}"
199
+ end
200
+
201
+ #
202
+ # decodes a base64 encoded SAML response for use in SloLogoutresponse tests
203
+ #
204
+ def decode_saml_response_payload(unauth_url)
205
+ payload = CGI.unescape(unauth_url.split("SAMLResponse=").last)
206
+ decoded = Base64.decode64(payload)
207
+
208
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
209
+ inflated = zstream.inflate(decoded)
210
+ zstream.finish
211
+ zstream.close
212
+ inflated
213
+ end
214
+
215
+ #
216
+ # decodes a base64 encoded SAML request for use in Logoutrequest tests
217
+ #
218
+ def decode_saml_request_payload(unauth_url)
219
+ payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
220
+ decoded = Base64.decode64(payload)
221
+
222
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
223
+ inflated = zstream.inflate(decoded)
224
+ zstream.finish
225
+ zstream.close
226
+ inflated
227
+ end
228
+
229
+ SCHEMA_DIR = File.expand_path(File.join(__FILE__, '../../lib/schemas'))
230
+
231
+ #
232
+ # validate an xml document against the given schema
233
+ #
234
+ def validate_xml!(document, schema)
235
+ Dir.chdir(SCHEMA_DIR) do
236
+ xsd = if schema.is_a? Nokogiri::XML::Schema
237
+ schema
238
+ else
239
+ Nokogiri::XML::Schema(File.read(schema))
240
+ end
241
+
242
+ xml = if document.is_a? Nokogiri::XML::Document
243
+ document
244
+ else
245
+ Nokogiri::XML(document) { |c| c.strict }
246
+ end
247
+
248
+ result = xsd.validate(xml)
249
+
250
+ if result.length != 0
251
+ raise "Schema validation failed! XSD validation errors: #{result.join(", ")}"
252
+ else
253
+ true
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,145 @@
1
+ require "test_helper"
2
+
3
+ class UtilsTest < Minitest::Test
4
+ describe ".format_cert" do
5
+ let(:formatted_certificate) do
6
+ read_certificate("formatted_certificate")
7
+ end
8
+
9
+ it "returns empty string when the cert is an empty string" do
10
+ cert = ""
11
+ assert_equal "", OneLogin::RubySaml::Utils.format_cert(cert)
12
+ end
13
+
14
+ it "returns nil when the cert is nil" do
15
+ cert = nil
16
+ assert_equal nil, OneLogin::RubySaml::Utils.format_cert(cert)
17
+ end
18
+
19
+ it "returns the certificate when it is valid" do
20
+ assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(formatted_certificate)
21
+ end
22
+
23
+ it "reformats the certificate when there are spaces and no line breaks" do
24
+ invalid_certificate1 = read_certificate("invalid_certificate1")
25
+ assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate1)
26
+ end
27
+
28
+ it "reformats the certificate when there are spaces and no headers" do
29
+ invalid_certificate2 = read_certificate("invalid_certificate2")
30
+ assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate2)
31
+ end
32
+
33
+ it "reformats the certificate when there line breaks and no headers" do
34
+ invalid_certificate3 = read_certificate("invalid_certificate3")
35
+ assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate3)
36
+ end
37
+ end
38
+
39
+ describe ".format_private_key" do
40
+ let(:formatted_private_key) do
41
+ read_certificate("formatted_private_key")
42
+ end
43
+
44
+ it "returns empty string when the private key is an empty string" do
45
+ private_key = ""
46
+ assert_equal "", OneLogin::RubySaml::Utils.format_private_key(private_key)
47
+ end
48
+
49
+ it "returns nil when the private key is nil" do
50
+ private_key = nil
51
+ assert_equal nil, OneLogin::RubySaml::Utils.format_private_key(private_key)
52
+ end
53
+
54
+ it "returns the private key when it is valid" do
55
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_private_key)
56
+ end
57
+
58
+ it "reformats the private key when there are spaces and no line breaks" do
59
+ invalid_private_key1 = read_certificate("invalid_private_key1")
60
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key1)
61
+ end
62
+
63
+ it "reformats the private key when there are spaces and no headers" do
64
+ invalid_private_key2 = read_certificate("invalid_private_key2")
65
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key2)
66
+ end
67
+
68
+ it "reformats the private key when there line breaks and no headers" do
69
+ invalid_private_key3 = read_certificate("invalid_private_key3")
70
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key3)
71
+ end
72
+
73
+ describe "an RSA public key" do
74
+ let(:formatted_rsa_private_key) do
75
+ read_certificate("formatted_rsa_private_key")
76
+ end
77
+
78
+ it "returns the private key when it is valid" do
79
+ assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_rsa_private_key)
80
+ end
81
+
82
+ it "reformats the private key when there are spaces and no line breaks" do
83
+ invalid_rsa_private_key1 = read_certificate("invalid_rsa_private_key1")
84
+ assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key1)
85
+ end
86
+
87
+ it "reformats the private key when there are spaces and no headers" do
88
+ invalid_rsa_private_key2 = read_certificate("invalid_rsa_private_key2")
89
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key2)
90
+ end
91
+
92
+ it "reformats the private key when there line breaks and no headers" do
93
+ invalid_rsa_private_key3 = read_certificate("invalid_rsa_private_key3")
94
+ assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key3)
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "build_query" do
100
+ it "returns the query string" do
101
+ params = {}
102
+ params[:type] = "SAMLRequest"
103
+ params[:data] = "PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8+"
104
+ params[:relay_state] = "http://example.com"
105
+ params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
106
+ query_string = OneLogin::RubySaml::Utils.build_query(params)
107
+ assert_equal "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1", query_string
108
+ end
109
+ end
110
+
111
+ describe "#verify_signature" do
112
+ before do
113
+ @params = {}
114
+ @params[:cert] = ruby_saml_cert
115
+ @params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
116
+ @params[:query_string] = "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"
117
+ end
118
+
119
+ it "returns true when the signature is valid" do
120
+ @params[:signature] = "uWJm/T4gKLYEsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4="
121
+ assert OneLogin::RubySaml::Utils.verify_signature(@params)
122
+ end
123
+
124
+ it "returns false when the signature is invalid" do
125
+ @params[:signature] = "uWJm/InVaLiDsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4="
126
+ assert !OneLogin::RubySaml::Utils.verify_signature(@params)
127
+ end
128
+ end
129
+
130
+ describe "#status_error_msg" do
131
+ it "returns a error msg with a status message" do
132
+ error_msg = "The status code of the Logout Response was not Success"
133
+ status_code = "urn:oasis:names:tc:SAML:2.0:status:Requester"
134
+ status_message = "The request could not be performed due to an error on the part of the requester."
135
+ status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
136
+ assert_equal = "The status code of the Logout Response was not Success, was Requester -> The request could not be performed due to an error on the part of the requester.", status_error_msg
137
+
138
+ status_error_msg2 = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code)
139
+ assert_equal = "The status code of the Logout Response was not Success, was Requester", status_error_msg2
140
+
141
+ status_error_msg3 = OneLogin::RubySaml::Utils.status_error_msg(error_msg)
142
+ assert_equal = "The status code of the Logout Response was not Success", status_error_msg3
143
+ end
144
+ end
145
+ end