ruby-saml 0.8.9 → 0.8.14

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.

Potentially problematic release.


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

Files changed (42) hide show
  1. data/Gemfile +11 -1
  2. data/Rakefile +0 -14
  3. data/lib/onelogin/ruby-saml/authrequest.rb +84 -18
  4. data/lib/onelogin/ruby-saml/logoutrequest.rb +93 -18
  5. data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -24
  6. data/lib/onelogin/ruby-saml/response.rb +206 -11
  7. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  8. data/lib/onelogin/ruby-saml/settings.rb +73 -12
  9. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +158 -0
  10. data/lib/onelogin/ruby-saml/utils.rb +169 -0
  11. data/lib/onelogin/ruby-saml/version.rb +1 -1
  12. data/lib/ruby-saml.rb +2 -1
  13. data/lib/xml_security.rb +332 -78
  14. data/test/certificates/ruby-saml-2.crt +15 -0
  15. data/test/certificates/ruby-saml.crt +14 -0
  16. data/test/certificates/ruby-saml.key +15 -0
  17. data/test/logoutrequest_test.rb +177 -44
  18. data/test/logoutresponse_test.rb +23 -28
  19. data/test/request_test.rb +100 -37
  20. data/test/response_test.rb +337 -129
  21. data/test/responses/adfs_response_xmlns.xml +45 -0
  22. data/test/responses/encrypted_new_attack.xml.base64 +1 -0
  23. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  24. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  25. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +51 -0
  26. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +49 -0
  27. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +1 -0
  28. data/test/responses/response_with_concealed_signed_assertion.xml +51 -0
  29. data/test/responses/response_with_doubled_signed_assertion.xml +49 -0
  30. data/test/responses/response_with_signed_assertion_3.xml +30 -0
  31. data/test/responses/response_with_signed_message_and_assertion.xml +34 -0
  32. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  33. data/test/responses/response_wrapped.xml.base64 +150 -0
  34. data/test/responses/valid_response.xml.base64 +1 -0
  35. data/test/responses/valid_response_without_x509certificate.xml.base64 +1 -0
  36. data/test/settings_test.rb +5 -5
  37. data/test/slo_logoutresponse_test.rb +226 -0
  38. data/test/test_helper.rb +117 -12
  39. data/test/utils_test.rb +10 -10
  40. data/test/xml_security_test.rb +354 -68
  41. metadata +64 -18
  42. checksums.yaml +0 -7
@@ -0,0 +1 @@
1
+ PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhiYzgyNmFmZC1lOWZlLWQzZmItZDg3NC1jNDcwMGMzZWYwYzgiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkl6NFpRbHMzQUpaRGIzczh2Y1VYLzNSYytGUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+UWhLSm1vbnlzUDFxbW5hN1MrZUUxTGMycktBampDMk9HclFPZ1NqUHBUb2N1bVE2aFlIa3pUU1pyN3QvSS9LVE9TdkhDUXFEMXJoNGxTMGpEUC9FdUhOQUN0azlZN2xsMlV5Z3U3MkwrYkZ0cVoyOURuOXJMa1NkR3JpK0k3SGh4TDM2N2RmQVNTaDYrc3k3V2V2RWRrTWZ3ZURRMkFYL3NhNkJCR2d6N1RFPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
@@ -0,0 +1 @@
1
+ pVZdd9o4EH3fc/Y/+LiPOcayDTb4BLoU0oSWfBDTbpuXPbI0Bie25Egi0Pz6lQ04kJI03X2CGY/u3LkjjXT8fpVnxgMImXLWNZ0GMt/3/vzjWOI8K8JrkAVnEgwdxGRYOrvmQrCQY5nKkOEcZKhIGPXPx6HbQCGWEoTSUObOkuL1NYXgihOemcZo2DWLZBWTtuvjhFrQScCiXhJbtB00LdIMECIeJIi0TePrlrPG0EulXMCISYWZ0i7kNC3kW6g5RW7ouiFyb0xjCFKlDKtq1VypIrRtXBSNfEFxg3FbSm4TXe4iBw3ItsVPedf8JyFN7DVjZAWg6SDHBYtgDFbQdmMSO14ce22zV8kWVlxEr8wgNyk4g4zPUtYgPLfLIPfY3o09pjKM0pkmtxBbtamsWS6Xy8bSa3Axs12EkI06to6hMp29M3W3DGO7HuiIJbyCG2DGWUpwlj5WJZ+DmnNq9LMZF6ma5y+AO7aDSnALVsQiTpO9M+0qxVOSiuQb4fa4CoktOcfOBrHEu4YEBDACxpfrUdd899b2VyVOBWYy4SKX++bvsQL2oJtTALXktjhN8PcAD6p2bP/McZjO9C78L+JthHsC+YqzBfRGj82bSSa9/qebYezJ9gP58s32rsnRx0m3IrAbXDlqydfms21TN3i9YjL//Cnn7Ie8cu5zhoPoCE6cMXHF5/7t7cC9PBWTy1l0e1VMOVnkE3/+/ezucRrdiEDZI/vz9DJ6OBtM7oeOmDezCN0Or+yTxdlFf6DuOt+DLHO//JgtAnd8FH9U9zduZ8g6YnwX0VORHo2Cs/lq7PkBTfpRNPeP5I/gb3g4oXfnyRKGE7f/TR8i/8OH09ljMD3p1uXs8NejbM/b20y2SGlT7lsDTsGolr0+sGQVHUYLQkDKqtE/g4b97SjcHOfVS8fZsb+djyMyhxybdWz662ArrcYdgZ9m4XqMdlqOH6PEs5otz7cclPgW8RNsdShtuR44zU6bvGlw/o+xVhnRIr4FojbWhdZzNDQ+6iOB1ctCOw2n8qTUSqrQUJecZn1KRSl6T+lN/ddu/k3mNfx+5gFnSVpilN1Yn73XO0zyMAYsQJgvAw2xwsYFV5fsUvQTBaJUz0M76gXra+caSFqkUMortn/rTXMI+dmnDQUdQdPysyyzfgCtClQNc55SOpuUb6C13aULmpazQF92SqRknX7vS91wyXPQgjdghfMig6rneBO0YVyveWbvodvPyqnzqTkrTxDkWiCjMn9xoUd6J2iEF6ptHQgdMQorfZ07ftIKUBIT5DkthAKMvFaLJoD92CNNn5i7pDRVBSt1wDXI9INHz9Peq28iEpIyTruv9M+SC3qlnzy6s0Cr26HgQtWCHQA/8G3PV4tWe7ejp55M27dM718=
@@ -1,12 +1,12 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class SettingsTest < Test::Unit::TestCase
3
+ class SettingsTest < Minitest::Test
4
4
 
5
- context "Settings" do
6
- setup do
5
+ describe "Settings" do
6
+ before do
7
7
  @settings = OneLogin::RubySaml::Settings.new
8
8
  end
9
- should "should provide getters and settings" do
9
+ it "should provide getters and settings" do
10
10
  accessors = [
11
11
  :assertion_consumer_service_url, :issuer, :sp_entity_id, :sp_name_qualifier,
12
12
  :idp_sso_target_url, :idp_cert_fingerprint, :name_identifier_format,
@@ -22,7 +22,7 @@ class SettingsTest < Test::Unit::TestCase
22
22
  end
23
23
  end
24
24
 
25
- should "create settings from hash" do
25
+ it "create settings from hash" do
26
26
 
27
27
  config = {
28
28
  :assertion_consumer_service_url => "http://app.muda.no/sso",
@@ -0,0 +1,226 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ class SloLogoutresponseTest < Minitest::Test
4
+
5
+ describe "SloLogoutresponse" do
6
+
7
+ let(:settings) { OneLogin::RubySaml::Settings.new }
8
+
9
+ before do
10
+ settings.idp_slo_target_url = "http://unauth.com/logout"
11
+ settings.name_identifier_value = "f00f00"
12
+ settings.compress_request = true
13
+ settings.certificate = ruby_saml_cert_text
14
+ settings.private_key = ruby_saml_key_text
15
+ end
16
+
17
+ it "create the deflated SAMLResponse URL parameter" do
18
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings)
19
+ assert_match /^http:\/\/unauth\.com\/logout\?SAMLResponse=/, unauth_url
20
+
21
+ inflated = decode_saml_response_payload(unauth_url)
22
+ assert_match /^<samlp:LogoutResponse/, inflated
23
+ end
24
+
25
+ it "support additional params" do
26
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { :hello => nil })
27
+ assert_match /&hello=$/, unauth_url
28
+
29
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { :foo => "bar" })
30
+ assert_match /&foo=bar$/, unauth_url
31
+
32
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { :RelayState => "http://idp.example.com" })
33
+ assert_match /&RelayState=http%3A%2F%2Fidp.example.com$/, unauth_url
34
+ end
35
+
36
+ it "RelayState cases" do
37
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { :RelayState => nil })
38
+ assert !unauth_url.include?('RelayState')
39
+
40
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { :RelayState => "http://example.com" })
41
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
42
+
43
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { 'RelayState' => nil })
44
+ assert !unauth_url.include?('RelayState')
45
+
46
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, nil, { 'RelayState' => "http://example.com" })
47
+ assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
48
+ end
49
+
50
+ it "set InResponseTo to the ID from the logout request" do
51
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, '_c0348950-935b-0131-1060-782bcb56fcaa')
52
+
53
+ inflated = decode_saml_response_payload(unauth_url)
54
+ assert_match /InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated
55
+ end
56
+
57
+ it "set a custom successful logout message on the response" do
58
+ unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, "Custom Logout Message")
59
+
60
+ inflated = decode_saml_response_payload(unauth_url)
61
+ assert_match /<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated
62
+ end
63
+
64
+ describe "when the settings indicate to sign (embedded) logout response" do
65
+
66
+ before do
67
+ settings.compress_response = false
68
+ settings.security[:logout_responses_signed] = true
69
+ settings.security[:embed_sign] = true
70
+ end
71
+
72
+ it "doesn't sign through create_xml_document" do
73
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
74
+ inflated = unauth_res.create_xml_document(settings).to_s
75
+
76
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
77
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
78
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
79
+ end
80
+
81
+ it "sign unsigned request" do
82
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
83
+ unauth_res_doc = unauth_res.create_xml_document(settings)
84
+ inflated = unauth_res_doc.to_s
85
+
86
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
87
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
88
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
89
+
90
+ inflated = unauth_res.sign_document(unauth_res_doc, settings).to_s
91
+
92
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
93
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
94
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
95
+ end
96
+
97
+ it "signs through create_logout_response_xml_doc" do
98
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
99
+ inflated = unauth_res.create_logout_response_xml_doc(settings).to_s
100
+
101
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
102
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
103
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
104
+ end
105
+
106
+ it "create a signed logout response" do
107
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message")
108
+
109
+ response_xml = Base64.decode64(params["SAMLResponse"])
110
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
111
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/, response_xml
112
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#sha1'\/>/, response_xml
113
+ end
114
+
115
+ it "create a signed logout response with 256 digest and signature methods" do
116
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
117
+ settings.security[:digest_method] = XMLSecurity::Document::SHA256
118
+
119
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message")
120
+
121
+ response_xml = Base64.decode64(params["SAMLResponse"])
122
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
123
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/, response_xml
124
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmlenc#sha256'\/>/, response_xml
125
+ end
126
+
127
+ it "create a signed logout response with 512 digest and signature method RSA_SHA384" do
128
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
129
+ settings.security[:digest_method] = XMLSecurity::Document::SHA512
130
+
131
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message")
132
+
133
+ response_xml = Base64.decode64(params["SAMLResponse"])
134
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
135
+ assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha384'\/>/, response_xml
136
+ assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmlenc#sha512'\/>/, response_xml
137
+ end
138
+ end
139
+
140
+ describe "#create_params when the settings indicate to sign the logout response" do
141
+
142
+ let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
143
+
144
+ before do
145
+ settings.compress_response = false
146
+ settings.security[:logout_responses_signed] = true
147
+ settings.security[:embed_sign] = false
148
+ end
149
+
150
+ it "create a signature parameter with RSA_SHA1 and validate it" do
151
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
152
+
153
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message", :RelayState => 'http://example.com')
154
+ assert params['SAMLResponse']
155
+ assert params[:RelayState]
156
+ assert params['Signature']
157
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
158
+
159
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
160
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
161
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
162
+
163
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
164
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA1
165
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
166
+ end
167
+
168
+ it "create a signature parameter with RSA_SHA256 /SHA256 and validate it" do
169
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
170
+
171
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message", :RelayState => 'http://example.com')
172
+ assert params['SAMLResponse']
173
+ assert params[:RelayState]
174
+ assert params['Signature']
175
+
176
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
177
+
178
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
179
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
180
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
181
+
182
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
183
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
184
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
185
+ end
186
+
187
+ it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
188
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
189
+
190
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message", :RelayState => 'http://example.com')
191
+ assert params['SAMLResponse']
192
+ assert params[:RelayState]
193
+ assert params['Signature']
194
+
195
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384
196
+
197
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
198
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
199
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
200
+
201
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
202
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA384
203
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
204
+ end
205
+
206
+ it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
207
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
208
+
209
+ params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, nil, "Custom Logout Message", :RelayState => 'http://example.com')
210
+ assert params['SAMLResponse']
211
+ assert params[:RelayState]
212
+ assert params['Signature']
213
+
214
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
215
+
216
+ query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}"
217
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
218
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
219
+
220
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
221
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA512
222
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
223
+ end
224
+ end
225
+ end
226
+ end
@@ -1,16 +1,22 @@
1
1
  require 'rubygems'
2
- require 'test/unit'
2
+ require 'minitest/autorun'
3
3
  require 'shoulda'
4
4
  require 'mocha/setup'
5
5
  require 'timecop'
6
6
 
7
+ if RUBY_VERSION < '1.9'
8
+ require 'uuid'
9
+ else
10
+ require 'securerandom'
11
+ end
12
+
7
13
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
14
  $LOAD_PATH.unshift(File.dirname(__FILE__))
9
15
  require 'ruby-saml'
10
16
 
11
17
  ENV["ruby-saml/testing"] = "1"
12
18
 
13
- class Test::Unit::TestCase
19
+ class Minitest::Test
14
20
  def fixture(document, base64 = true)
15
21
  response = Dir.glob(File.join(File.dirname(__FILE__), "responses", "#{document}*")).first
16
22
  if base64 && response =~ /\.xml$/
@@ -20,32 +26,48 @@ class Test::Unit::TestCase
20
26
  end
21
27
  end
22
28
 
29
+ def random_id
30
+ RUBY_VERSION < '1.9' ? "_#{UUID.new.generate}" : "_#{SecureRandom.uuid}"
31
+ end
32
+
33
+ def read_invalid_response(response)
34
+ File.read(File.join(File.dirname(__FILE__), "responses", "invalids", response))
35
+ end
36
+
37
+ def read_response(response)
38
+ File.read(File.join(File.dirname(__FILE__), "responses", response))
39
+ end
40
+
41
+ def read_certificate(certificate)
42
+ File.read(File.join(File.dirname(__FILE__), "certificates", certificate))
43
+ end
44
+
23
45
  def response_document
24
- @response_document ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response1.xml.base64'))
46
+ @response_document ||= read_response('response1.xml.base64')
25
47
  end
26
48
 
27
49
  def response_document_2
28
- @response_document2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response2.xml.base64'))
50
+ @response_document2 ||= read_response('response2.xml.base64')
29
51
  end
30
52
 
31
53
  def response_document_3
32
- @response_document3 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response3.xml.base64'))
54
+ @response_document3 ||= read_response('response3.xml.base64')
33
55
  end
34
56
 
35
57
  def response_document_4
36
- @response_document4 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response4.xml.base64'))
58
+ @response_document4 ||= read_response('response4.xml.base64')
37
59
  end
38
60
 
39
61
  def response_document_5
40
- @response_document5 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response5.xml.base64'))
62
+ @response_document5 ||= read_response('response5.xml.base64')
41
63
  end
42
64
 
43
65
  def r1_response_document_6
44
- @response_document6 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'r1_response6.xml.base64'))
66
+ @response_document6 ||= read_response('r1_response6.xml.base64')
45
67
  end
46
68
 
47
69
  def ampersands_response
48
- @ampersands_resposne ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_with_ampersands.xml.base64'))
70
+ @ampersands_resposne ||= read_response('response_with_ampersands.xml.base64')
49
71
  end
50
72
 
51
73
  def response_document_6
@@ -55,6 +77,22 @@ class Test::Unit::TestCase
55
77
  Base64.encode64(doc)
56
78
  end
57
79
 
80
+ def response_document_wrapped
81
+ @response_document_wrapped ||= read_response("response_wrapped.xml.base64")
82
+ end
83
+
84
+ def response_document_valid_signed
85
+ response_document_valid_signed ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'valid_response.xml.base64'))
86
+ end
87
+
88
+ def response_document_valid_signed_without_x509certificate
89
+ @response_document_valid_signed_without_x509certificate ||= read_response("valid_response_without_x509certificate.xml.base64")
90
+ end
91
+
92
+ def response_document_without_recipient
93
+ @response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64")
94
+ end
95
+
58
96
  def wrapped_response_2
59
97
  @wrapped_response_2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'wrapped_response_2.xml.base64'))
60
98
  end
@@ -63,12 +101,24 @@ class Test::Unit::TestCase
63
101
  @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
64
102
  end
65
103
 
104
+ def signature_fingerprint_valid_res
105
+ @signature_fingerprint1 ||= "4b68c453c7d994aad9025c99d5efcf566287fe8d"
106
+ end
107
+
66
108
  def signature_1
67
- @signature1 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'certificate1'))
109
+ @signature1 ||= read_certificate('certificate1')
68
110
  end
69
111
 
70
112
  def r1_signature_2
71
- @signature2 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'r1_certificate2_base64'))
113
+ @signature2 ||= read_certificate('r1_certificate2_base64')
114
+ end
115
+
116
+ def valid_cert
117
+ @signature_valid_cert ||= read_certificate('ruby-saml.crt')
118
+ end
119
+
120
+ def valid_key
121
+ @signature_valid_cert ||= read_certificate('ruby-saml.key')
72
122
  end
73
123
 
74
124
  def response_with_multiple_attribute_statements
@@ -76,7 +126,62 @@ class Test::Unit::TestCase
76
126
  end
77
127
 
78
128
  def response_multiple_attr_values
79
- @response_multiple_attr_values = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
129
+ @response_multiple_attr_values = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values))
130
+ end
131
+
132
+ def ruby_saml_cert
133
+ @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
134
+ end
135
+
136
+ def ruby_saml_cert2
137
+ @ruby_saml_cert2 ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text2)
138
+ end
139
+
140
+ def ruby_saml_cert_fingerprint
141
+ @ruby_saml_cert_fingerprint ||= Digest::SHA1.hexdigest(ruby_saml_cert.to_der).scan(/../).join(":")
142
+ end
143
+
144
+ def ruby_saml_cert_text
145
+ read_certificate("ruby-saml.crt")
146
+ end
147
+
148
+ def ruby_saml_cert_text2
149
+ read_certificate("ruby-saml-2.crt")
150
+ end
151
+
152
+ def ruby_saml_key_text
153
+ read_certificate("ruby-saml.key")
80
154
  end
81
155
 
156
+ def ruby_saml_key
157
+ @ruby_saml_key ||= OpenSSL::PKey::RSA.new(ruby_saml_key_text)
158
+ end
159
+
160
+ def read_certificate(certificate)
161
+ File.read(File.join(File.dirname(__FILE__), "certificates", certificate))
162
+ end
163
+
164
+ def decode_saml_request_payload(unauth_url)
165
+ payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
166
+ decoded = Base64.decode64(payload)
167
+
168
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
169
+ inflated = zstream.inflate(decoded)
170
+ zstream.finish
171
+ zstream.close
172
+ inflated
173
+ end
174
+
175
+ # decodes a base64 encoded SAML response for use in SloLogoutresponse tests
176
+ #
177
+ def decode_saml_response_payload(unauth_url)
178
+ payload = CGI.unescape(unauth_url.split("SAMLResponse=").last)
179
+ decoded = Base64.decode64(payload)
180
+
181
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
182
+ inflated = zstream.inflate(decoded)
183
+ zstream.finish
184
+ zstream.close
185
+ inflated
186
+ end
82
187
  end