ruby-samlnechotech 0.7.23

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 (51) hide show
  1. data/.document +5 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE +19 -0
  6. data/README.md +128 -0
  7. data/Rakefile +41 -0
  8. data/lib/onelogin/ruby-samlnechotech/authrequest.rb +84 -0
  9. data/lib/onelogin/ruby-samlnechotech/logging.rb +26 -0
  10. data/lib/onelogin/ruby-samlnechotech/logoutrequest.rb +82 -0
  11. data/lib/onelogin/ruby-samlnechotech/logoutresponse.rb +154 -0
  12. data/lib/onelogin/ruby-samlnechotech/metadata.rb +66 -0
  13. data/lib/onelogin/ruby-samlnechotech/response.rb +186 -0
  14. data/lib/onelogin/ruby-samlnechotech/settings.rb +27 -0
  15. data/lib/onelogin/ruby-samlnechotech/validation_error.rb +7 -0
  16. data/lib/onelogin/ruby-samlnechotech/version.rb +5 -0
  17. data/lib/ruby-samlnechotech.rb +9 -0
  18. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  19. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  20. data/lib/schemas/xenc_schema.xsd +146 -0
  21. data/lib/schemas/xmldsig_schema.xsd +318 -0
  22. data/lib/xml_security.rb +169 -0
  23. data/ruby-samlnechotech.gemspec +29 -0
  24. data/test/certificates/certificate1 +12 -0
  25. data/test/certificates/r1_certificate2_base64 +1 -0
  26. data/test/logoutrequest_test.rb +111 -0
  27. data/test/logoutresponse_test.rb +116 -0
  28. data/test/request_test.rb +97 -0
  29. data/test/response_test.rb +247 -0
  30. data/test/responses/adfs_response_sha1.xml +46 -0
  31. data/test/responses/adfs_response_sha256.xml +46 -0
  32. data/test/responses/adfs_response_sha384.xml +46 -0
  33. data/test/responses/adfs_response_sha512.xml +46 -0
  34. data/test/responses/logoutresponse_fixtures.rb +67 -0
  35. data/test/responses/no_signature_ns.xml +48 -0
  36. data/test/responses/open_saml_response.xml +56 -0
  37. data/test/responses/r1_response6.xml.base64 +1 -0
  38. data/test/responses/response1.xml.base64 +1 -0
  39. data/test/responses/response2.xml.base64 +79 -0
  40. data/test/responses/response3.xml.base64 +66 -0
  41. data/test/responses/response4.xml.base64 +93 -0
  42. data/test/responses/response5.xml.base64 +102 -0
  43. data/test/responses/response_with_ampersands.xml +139 -0
  44. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  45. data/test/responses/simple_saml_php.xml +71 -0
  46. data/test/responses/starfield_response.xml.base64 +1 -0
  47. data/test/responses/wrapped_response_2.xml.base64 +150 -0
  48. data/test/settings_test.rb +46 -0
  49. data/test/test_helper.rb +75 -0
  50. data/test/xml_security_test.rb +160 -0
  51. metadata +172 -0
@@ -0,0 +1,169 @@
1
+ # The contents of this file are subject to the terms
2
+ # of the Common Development and Distribution License
3
+ # (the License). You may not use this file except in
4
+ # compliance with the License.
5
+ #
6
+ # You can obtain a copy of the License at
7
+ # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
+ # opensso/legal/CDDLv1.0.txt
9
+ # See the License for the specific language governing
10
+ # permission and limitations under the License.
11
+ #
12
+ # When distributing Covered Code, include this CDDL
13
+ # Header Notice in each file and include the License file
14
+ # at opensso/legal/CDDLv1.0.txt.
15
+ # If applicable, add the following below the CDDL Header,
16
+ # with the fields enclosed by brackets [] replaced by
17
+ # your own identifying information:
18
+ # "Portions Copyrighted [year] [name of copyright owner]"
19
+ #
20
+ # $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
21
+ #
22
+ # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
+ # Portions Copyrighted 2007 Todd W Saxton.
24
+
25
+ require 'rubygems'
26
+ require "rexml/document"
27
+ require "rexml/xpath"
28
+ require "openssl"
29
+ require 'nokogiri'
30
+ require "digest/sha1"
31
+ require "digest/sha2"
32
+ require "onelogin/ruby-samlnechotech/validation_error"
33
+
34
+ module XMLSecurity
35
+
36
+ class SignedDocument < REXML::Document
37
+ C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
38
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
39
+
40
+ attr_accessor :signed_element_id
41
+
42
+ def initialize(response)
43
+ super(response)
44
+ extract_signed_element_id
45
+ end
46
+
47
+ def validate(idp_cert_fingerprint, soft = true)
48
+ # get cert from response
49
+ cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
50
+ raise Onelogin::Saml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)") unless cert_element
51
+ base64_cert = cert_element.text
52
+ cert_text = Base64.decode64(base64_cert)
53
+ cert = OpenSSL::X509::Certificate.new(cert_text)
54
+
55
+ # check cert matches registered idp cert
56
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
57
+
58
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
59
+ return soft ? false : (raise Onelogin::Saml::ValidationError.new("Fingerprint mismatch"))
60
+ end
61
+
62
+ validate_doc(base64_cert, soft)
63
+ end
64
+
65
+ def validate_doc(base64_cert, soft = true)
66
+ # validate references
67
+
68
+ # check for inclusive namespaces
69
+ inclusive_namespaces = extract_inclusive_namespaces
70
+
71
+ document = Nokogiri.parse(self.to_s)
72
+
73
+ # create a working copy so we don't modify the original
74
+ @working_copy ||= REXML::Document.new(self.to_s).root
75
+
76
+ # store and remove signature node
77
+ @sig_element ||= begin
78
+ element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
79
+ element.remove
80
+ end
81
+
82
+
83
+ # verify signature
84
+ signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
85
+ noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
86
+ noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
87
+ canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
88
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
89
+ noko_sig_element.remove
90
+
91
+ # check digests
92
+ REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
93
+ uri = ref.attributes.get_attribute("URI").value
94
+
95
+ hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
96
+ canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
97
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
98
+
99
+ digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
100
+
101
+ hash = digest_algorithm.digest(canon_hashed_element)
102
+ digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
103
+
104
+ unless digests_match?(hash, digest_value)
105
+ return soft ? false : (raise Onelogin::Saml::ValidationError.new("Digest mismatch"))
106
+ end
107
+ end
108
+
109
+ base64_signature = REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
110
+ signature = Base64.decode64(base64_signature)
111
+
112
+ # get certificate object
113
+ cert_text = Base64.decode64(base64_cert)
114
+ cert = OpenSSL::X509::Certificate.new(cert_text)
115
+
116
+ # signature method
117
+ signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
118
+
119
+ unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
120
+ return soft ? false : (raise Onelogin::Saml::ValidationError.new("Key validation error"))
121
+ end
122
+
123
+ return true
124
+ end
125
+
126
+ private
127
+
128
+ def digests_match?(hash, digest_value)
129
+ hash == digest_value
130
+ end
131
+
132
+ def extract_signed_element_id
133
+ reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
134
+ self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
135
+ end
136
+
137
+ def canon_algorithm(element)
138
+ algorithm = element.attribute('Algorithm').value if element
139
+ case algorithm
140
+ when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
141
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
142
+ when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
143
+ else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
144
+ end
145
+ end
146
+
147
+ def algorithm(element)
148
+ algorithm = element.attribute("Algorithm").value if element
149
+ algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
150
+ case algorithm
151
+ when 256 then OpenSSL::Digest::SHA256
152
+ when 384 then OpenSSL::Digest::SHA384
153
+ when 512 then OpenSSL::Digest::SHA512
154
+ else
155
+ OpenSSL::Digest::SHA1
156
+ end
157
+ end
158
+
159
+ def extract_inclusive_namespaces
160
+ if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
161
+ prefix_list = element.attributes.get_attribute("PrefixList").value
162
+ prefix_list.split(" ")
163
+ else
164
+ []
165
+ end
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'onelogin/ruby-samlnechotech/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'ruby-samlnechotech'
6
+ s.version = Onelogin::Saml::VERSION
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.authors = ["OneLogin LLC, beekermememe"]
10
+ s.date = Time.now.strftime("%Y-%m-%d")
11
+ s.description = %q{SAML toolkit for Ruby on Rails forked and modified by beekermememe}
12
+ s.email = %q{nechotech@gmail.com}
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README.md"
16
+ ]
17
+ s.files = `git ls-files`.split("\n")
18
+ s.homepage = %q{https://github.com/beekermememe/ruby-saml}
19
+ s.rubyforge_project = %q{http://www.rubygems.org/gems/ruby-samlnechotech}
20
+ s.rdoc_options = ["--charset=UTF-8"]
21
+ s.require_paths = ["lib"]
22
+ s.rubygems_version = %q{1.3.7}
23
+ s.summary = %q{SAML Ruby Tookit}
24
+ s.test_files = `git ls-files test/*`.split("\n")
25
+
26
+ s.add_runtime_dependency("canonix", ["0.1.1"])
27
+ s.add_runtime_dependency("uuid", ["~> 2.3"])
28
+ s.add_runtime_dependency("nokogiri", [">= 1.5.0"])
29
+ end
@@ -0,0 +1,12 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIBrTCCAaGgAwIBAgIBATADBgEAMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
3
+ YWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxv
4
+ Z2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMB4XDTEwMTAxMTIxMTUxMloX
5
+ DTE1MTAxMTIxMTUxMlowZzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju
6
+ aWExFTATBgNVBAcMDFNhbnRhIE1vbmljYTERMA8GA1UECgwIT25lTG9naW4xGTAX
7
+ BgNVBAMMEGFwcC5vbmVsb2dpbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
8
+ AoGBAMPmjfjy7L35oDpeBXBoRVCgktPkLno9DOEWB7MgYMMVKs2B6ymWQLEWrDug
9
+ MK1hkzWFhIb5fqWLGbWy0J0veGR9/gHOQG+rD/I36xAXnkdiXXhzoiAG/zQxM0ed
10
+ MOUf40n314FC8moErcUg6QabttzesO59HFz6shPuxcWaVAgxAgMBAAEwAwYBAAMB
11
+ AA==
12
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ MIIEYTCCA0mgAwIBAgIJAMax+2BoUJmCMA0GCSqGSIb3DQEBBQUAMIHGMQswCQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xIzAhBgNVBAoMGldlbGxzcHJpbmcgV29ybGR3aWRlLCBJbmMuMRwwGgYDVQQLDBNTeXN0ZW1zIEVuZ2luZWVyaW5nMSQwIgYDVQQDDBtzc28ud2VsbHNwcmluZ3dvcmxkd2lkZS5jb20xKTAnBgkqhkiG9w0BCQEWGml0QHdlbGxzcHJpbmd3b3JsZHdpZGUuY29tMB4XDTEzMDIyNzIzNTUwOFoXDTIzMDIyNzIzNTUwOFowgcYxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhJbGxpbm9pczEQMA4GA1UEBwwHQ2hpY2FnbzEjMCEGA1UECgwaV2VsbHNwcmluZyBXb3JsZHdpZGUsIEluYy4xHDAaBgNVBAsME1N5c3RlbXMgRW5naW5lZXJpbmcxJDAiBgNVBAMMG3Nzby53ZWxsc3ByaW5nd29ybGR3aWRlLmNvbTEpMCcGCSqGSIb3DQEJARYaaXRAd2VsbHNwcmluZ3dvcmxkd2lkZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzNhrDaXa00JAIRaxEyDrK/Zjj8bBTQD5dPgugDndYf1AOpzSGpGFU+lPu0QRv0o66K64HrF24FATWI18Q6aZ+xX8QbuBrfia6hOFef29Sk5paS9+DcDCmisuNpl84kbbiazy6S6cFtcdrG9/cr2iXtYmIzz7EfUcP/UVAp24ZW7dWhcvxoqxF9n6Fj94N+rA0dmUFUGz6glm7us3p36xbkiUMpgr3feD/9P34H+2YFsQ2b2DblDI5Z7YULHxBsl5nuhPLFuPN1olcWQBsJYO6iHElFRH+487L2yZ1mLVXKI0LFb/w1rAJpPeUc8E5s1MATAjNx3wPwwqgw30sKtoXAgMBAAGjUDBOMB0GA1UdDgQWBBSrGGV9w3hGXTafkJLUaWBsWiDGaTAfBgNVHSMEGDAWgBSrGGV9w3hGXTafkJLUaWBsWiDGaTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCcvZV0VIwIRD4C5CItFwfNRF2LRBmYiNh4FJbo0wESbH0cWT9vXNdcjcx6PHrIj7ICErSCR5eZmIrSgLEBEkptjVsiFsHWSvMHv37WaHwyhZWnhutss32aP9+ifxQ1lzwm54jZWZsVVVFQH155BDsVeU1UwEhvcCExFa7RNjyvqQrZmyQwMFSzL1cQp0humu0hHLtAI7E32lp5itw6kTOfyhjB8d1bzBVZe6RY64RxOPEcx+9hkrHmfCohdt644jRtPdLTvqqxpscYGD+L2QOt1HpbGgAdcgUZeUHo/eosqpwDOoyuFepz7JzqMncxFN//NmjnFGVZdGR+6bTxKUKq
@@ -0,0 +1,111 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ class RequestTest < Test::Unit::TestCase
4
+
5
+ context "Logoutrequest" do
6
+ settings = Onelogin::Saml::Settings.new
7
+
8
+ should "create the deflated SAMLRequest URL parameter" do
9
+ settings.idp_slo_target_url = "http://unauth.com/logout"
10
+ settings.name_identifier_value = "f00f00"
11
+
12
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
13
+ assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/
14
+
15
+ inflated = decode_saml_request_payload(unauth_url)
16
+
17
+ assert_match /^<samlp:LogoutRequest/, inflated
18
+ end
19
+
20
+ should "support additional params" do
21
+
22
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings, { :hello => nil })
23
+ assert unauth_url =~ /&hello=$/
24
+
25
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings, { :foo => "bar" })
26
+ assert unauth_url =~ /&foo=bar$/
27
+ end
28
+
29
+ should "set sessionindex" do
30
+ settings.idp_slo_target_url = "http://example.com"
31
+ sessionidx = UUID.new.generate
32
+ settings.sessionindex = sessionidx
33
+
34
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings, { :name_id => "there" })
35
+ inflated = decode_saml_request_payload(unauth_url)
36
+
37
+ assert_match /<samlp:SessionIndex/, inflated
38
+ assert_match %r(#{sessionidx}</samlp:SessionIndex>), inflated
39
+ end
40
+
41
+ should "set name_identifier_value" do
42
+ settings = Onelogin::Saml::Settings.new
43
+ settings.idp_slo_target_url = "http://example.com"
44
+ settings.name_identifier_format = "transient"
45
+ name_identifier_value = "abc123"
46
+ settings.name_identifier_value = name_identifier_value
47
+
48
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings, { :name_id => "there" })
49
+ inflated = decode_saml_request_payload(unauth_url)
50
+
51
+ assert_match /<saml:NameID/, inflated
52
+ assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
53
+ end
54
+
55
+ should "require name_identifier_value" do
56
+ settings = Onelogin::Saml::Settings.new
57
+ settings.idp_slo_target_url = "http://example.com"
58
+ settings.name_identifier_format = nil
59
+
60
+ assert_raises(Onelogin::Saml::ValidationError) { Onelogin::Saml::Logoutrequest.new.create(settings) }
61
+ end
62
+
63
+ context "when the target url doesn't contain a query string" do
64
+ should "create the SAMLRequest parameter correctly" do
65
+ settings = Onelogin::Saml::Settings.new
66
+ settings.idp_slo_target_url = "http://example.com"
67
+ settings.name_identifier_value = "f00f00"
68
+
69
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
70
+ assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/
71
+ end
72
+ end
73
+
74
+ context "when the target url contains a query string" do
75
+ should "create the SAMLRequest parameter correctly" do
76
+ settings = Onelogin::Saml::Settings.new
77
+ settings.idp_slo_target_url = "http://example.com?field=value"
78
+ settings.name_identifier_value = "f00f00"
79
+
80
+ unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
81
+ assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
82
+ end
83
+ end
84
+
85
+ context "consumation of logout may need to track the transaction" do
86
+ should "have access to the request uuid" do
87
+ settings = Onelogin::Saml::Settings.new
88
+ settings.idp_slo_target_url = "http://example.com?field=value"
89
+ settings.name_identifier_value = "f00f00"
90
+
91
+ unauth_req = Onelogin::Saml::Logoutrequest.new
92
+ unauth_url = unauth_req.create(settings)
93
+
94
+ inflated = decode_saml_request_payload(unauth_url)
95
+ assert_match %r[ID='#{unauth_req.uuid}'], inflated
96
+ end
97
+ end
98
+ end
99
+
100
+ def decode_saml_request_payload(unauth_url)
101
+ payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
102
+ decoded = Base64.decode64(payload)
103
+
104
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
105
+ inflated = zstream.inflate(decoded)
106
+ zstream.finish
107
+ zstream.close
108
+ inflated
109
+ end
110
+
111
+ end
@@ -0,0 +1,116 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+ require 'rexml/document'
3
+ require 'responses/logoutresponse_fixtures'
4
+ class RubySamlTest < Test::Unit::TestCase
5
+
6
+ context "Logoutresponse" do
7
+ context "#new" do
8
+ should "raise an exception when response is initialized with nil" do
9
+ assert_raises(ArgumentError) { Onelogin::Saml::Logoutresponse.new(nil) }
10
+ end
11
+ should "default to empty settings" do
12
+ logoutresponse = Onelogin::Saml::Logoutresponse.new( valid_response)
13
+ assert logoutresponse.settings.nil?
14
+ end
15
+ should "accept constructor-injected settings" do
16
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings)
17
+ assert !logoutresponse.settings.nil?
18
+ end
19
+ should "accept constructor-injected options" do
20
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, nil, { :foo => :bar} )
21
+ assert !logoutresponse.options.empty?
22
+ end
23
+ should "support base64 encoded responses" do
24
+ expected_response = valid_response
25
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(Base64.encode64(expected_response), settings)
26
+
27
+ assert_equal expected_response, logoutresponse.response
28
+ end
29
+ end
30
+
31
+ context "#validate" do
32
+ should "validate the response" do
33
+ in_relation_to_request_id = random_id
34
+
35
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
36
+
37
+ assert logoutresponse.validate
38
+
39
+ assert_equal settings.issuer, logoutresponse.issuer
40
+ assert_equal in_relation_to_request_id, logoutresponse.in_response_to
41
+
42
+ assert logoutresponse.success?
43
+ end
44
+
45
+ should "invalidate responses with wrong id when given option :matches_uuid" do
46
+
47
+ expected_request_id = "_some_other_expected_uuid"
48
+ opts = { :matches_request_id => expected_request_id}
49
+
50
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings, opts)
51
+
52
+ assert !logoutresponse.validate
53
+ assert_not_equal expected_request_id, logoutresponse.in_response_to
54
+ end
55
+
56
+ should "invalidate responses with wrong request status" do
57
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, settings)
58
+
59
+ assert !logoutresponse.validate
60
+ assert !logoutresponse.success?
61
+ end
62
+ end
63
+
64
+ context "#validate!" do
65
+ should "validates good responses" do
66
+ in_relation_to_request_id = random_id
67
+
68
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
69
+
70
+ logoutresponse.validate!
71
+ end
72
+
73
+ should "raises validation error when matching for wrong request id" do
74
+
75
+ expected_request_id = "_some_other_expected_id"
76
+ opts = { :matches_request_id => expected_request_id}
77
+
78
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings, opts)
79
+
80
+ assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
81
+ end
82
+
83
+ should "raise validation error for wrong request status" do
84
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, settings)
85
+
86
+ assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
87
+ end
88
+
89
+ should "raise validation error when in bad state" do
90
+ # no settings
91
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response)
92
+ assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
93
+ end
94
+
95
+ should "raise validation error when in lack of issuer setting" do
96
+ bad_settings = settings
97
+ bad_settings.issuer = nil
98
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, bad_settings)
99
+ assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
100
+ end
101
+
102
+ should "raise error for invalid xml" do
103
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(invalid_xml_response, settings)
104
+
105
+ assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ # logoutresponse fixtures
112
+ def random_id
113
+ "_#{UUID.new.generate}"
114
+ end
115
+
116
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ class RequestTest < Test::Unit::TestCase
4
+
5
+ context "Authrequest" do
6
+ should "create the deflated SAMLRequest URL parameter" do
7
+ settings = Onelogin::Saml::Settings.new
8
+ settings.idp_sso_target_url = "http://example.com"
9
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
10
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
11
+ payload = CGI.unescape(auth_url.split("=").last)
12
+ decoded = Base64.decode64(payload)
13
+
14
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
15
+ inflated = zstream.inflate(decoded)
16
+ zstream.finish
17
+ zstream.close
18
+
19
+ assert_match /^<samlp:AuthnRequest/, inflated
20
+ end
21
+
22
+ should "create the deflated SAMLRequest URL parameter including the Destination" do
23
+ settings = Onelogin::Saml::Settings.new
24
+ settings.idp_sso_target_url = "http://example.com"
25
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
26
+ payload = CGI.unescape(auth_url.split("=").last)
27
+ decoded = Base64.decode64(payload)
28
+
29
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
30
+ inflated = zstream.inflate(decoded)
31
+ zstream.finish
32
+ zstream.close
33
+
34
+ assert_match /<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated
35
+ end
36
+
37
+ should "create the SAMLRequest URL parameter without deflating" do
38
+ settings = Onelogin::Saml::Settings.new
39
+ settings.compress_request = false
40
+ settings.idp_sso_target_url = "http://example.com"
41
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
42
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
43
+ payload = CGI.unescape(auth_url.split("=").last)
44
+ decoded = Base64.decode64(payload)
45
+
46
+ assert_match /^<samlp:AuthnRequest/, decoded
47
+ end
48
+
49
+ should "create the SAMLRequest URL parameter with IsPassive" do
50
+ settings = Onelogin::Saml::Settings.new
51
+ settings.idp_sso_target_url = "http://example.com"
52
+ settings.passive = true
53
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
54
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
55
+ payload = CGI.unescape(auth_url.split("=").last)
56
+ decoded = Base64.decode64(payload)
57
+
58
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
59
+ inflated = zstream.inflate(decoded)
60
+ zstream.finish
61
+ zstream.close
62
+
63
+ assert_match /<samlp:AuthnRequest[^<]* IsPassive='true'/, inflated
64
+ end
65
+
66
+ should "accept extra parameters" do
67
+ settings = Onelogin::Saml::Settings.new
68
+ settings.idp_sso_target_url = "http://example.com"
69
+
70
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings, { :hello => "there" })
71
+ assert auth_url =~ /&hello=there$/
72
+
73
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings, { :hello => nil })
74
+ assert auth_url =~ /&hello=$/
75
+ end
76
+
77
+ context "when the target url doesn't contain a query string" do
78
+ should "create the SAMLRequest parameter correctly" do
79
+ settings = Onelogin::Saml::Settings.new
80
+ settings.idp_sso_target_url = "http://example.com"
81
+
82
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
83
+ assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/
84
+ end
85
+ end
86
+
87
+ context "when the target url contains a query string" do
88
+ should "create the SAMLRequest parameter correctly" do
89
+ settings = Onelogin::Saml::Settings.new
90
+ settings.idp_sso_target_url = "http://example.com?field=value"
91
+
92
+ auth_url = Onelogin::Saml::Authrequest.new.create(settings)
93
+ assert auth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
94
+ end
95
+ end
96
+ end
97
+ end