ruby-saml 0.8.18 → 0.9
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +63 -373
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -122
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -155
- data/test/logoutresponse_test.rb +43 -32
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +102 -99
- data/test/response_test.rb +181 -495
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +7 -8
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +40 -50
- data/test/slo_logoutresponse_test.rb +86 -185
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +30 -81
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
data/test/logoutrequest_test.rb
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class RequestTest < Test::Unit::TestCase
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
context "Logoutrequest" do
|
|
6
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
should "create the deflated SAMLRequest URL parameter" do
|
|
9
9
|
settings.idp_slo_target_url = "http://unauth.com/logout"
|
|
10
10
|
settings.name_identifier_value = "f00f00"
|
|
11
|
-
end
|
|
12
11
|
|
|
13
|
-
it "create the deflated SAMLRequest URL parameter" do
|
|
14
12
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
|
15
13
|
assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/
|
|
16
14
|
|
|
@@ -19,7 +17,8 @@ class LogoutRequestTest < Minitest::Test
|
|
|
19
17
|
assert_match /^<samlp:LogoutRequest/, inflated
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
should "support additional params" do
|
|
21
|
+
|
|
23
22
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :hello => nil })
|
|
24
23
|
assert unauth_url =~ /&hello=$/
|
|
25
24
|
|
|
@@ -27,8 +26,9 @@ class LogoutRequestTest < Minitest::Test
|
|
|
27
26
|
assert unauth_url =~ /&foo=bar$/
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
should "set sessionindex" do
|
|
30
|
+
settings.idp_slo_target_url = "http://example.com"
|
|
31
|
+
sessionidx = UUID.new.generate
|
|
32
32
|
settings.sessionindex = sessionidx
|
|
33
33
|
|
|
34
34
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :name_id => "there" })
|
|
@@ -38,7 +38,9 @@ class LogoutRequestTest < Minitest::Test
|
|
|
38
38
|
assert_match %r(#{sessionidx}</samlp:SessionIndex>), inflated
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
should "set name_identifier_value" do
|
|
42
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
43
|
+
settings.idp_slo_target_url = "http://example.com"
|
|
42
44
|
settings.name_identifier_format = "transient"
|
|
43
45
|
name_identifier_value = "abc123"
|
|
44
46
|
settings.name_identifier_value = name_identifier_value
|
|
@@ -50,25 +52,33 @@ class LogoutRequestTest < Minitest::Test
|
|
|
50
52
|
assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
context "when the target url doesn't contain a query string" do
|
|
56
|
+
should "create the SAMLRequest parameter correctly" do
|
|
57
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
55
58
|
settings.idp_slo_target_url = "http://example.com"
|
|
59
|
+
settings.name_identifier_value = "f00f00"
|
|
60
|
+
|
|
56
61
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
|
57
62
|
assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/
|
|
58
63
|
end
|
|
59
64
|
end
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
context "when the target url contains a query string" do
|
|
67
|
+
should "create the SAMLRequest parameter correctly" do
|
|
68
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
63
69
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
|
70
|
+
settings.name_identifier_value = "f00f00"
|
|
71
|
+
|
|
64
72
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
|
65
73
|
assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
|
|
66
74
|
end
|
|
67
75
|
end
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
context "consumation of logout may need to track the transaction" do
|
|
78
|
+
should "have access to the request uuid" do
|
|
79
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
71
80
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
|
81
|
+
settings.name_identifier_value = "f00f00"
|
|
72
82
|
|
|
73
83
|
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
|
74
84
|
unauth_url = unauth_req.create(settings)
|
|
@@ -78,178 +88,81 @@ class LogoutRequestTest < Minitest::Test
|
|
|
78
88
|
end
|
|
79
89
|
end
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
context "when the settings indicate to sign (embebed) the logout request" do
|
|
92
|
+
should "created a signed logout request" do
|
|
93
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
94
|
+
settings.idp_slo_target_url = "http://example.com?field=value"
|
|
95
|
+
settings.name_identifier_value = "f00f00"
|
|
85
96
|
# sign the logout request
|
|
86
97
|
settings.security[:logout_requests_signed] = true
|
|
87
98
|
settings.security[:embed_sign] = true
|
|
88
99
|
settings.certificate = ruby_saml_cert_text
|
|
89
100
|
settings.private_key = ruby_saml_key_text
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it "doesn't sign through create_xml_document" do
|
|
93
|
-
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
|
94
|
-
inflated = unauth_req.create_xml_document(settings).to_s
|
|
95
|
-
|
|
96
|
-
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
|
97
|
-
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
|
98
|
-
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "sign unsigned request" do
|
|
102
|
-
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
|
103
|
-
unauth_req_doc = unauth_req.create_xml_document(settings)
|
|
104
|
-
inflated = unauth_req_doc.to_s
|
|
105
|
-
|
|
106
|
-
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
|
107
|
-
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
|
108
|
-
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
|
109
|
-
|
|
110
|
-
inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
|
|
111
|
-
|
|
112
|
-
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
|
113
|
-
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
|
114
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it "signs through create_logout_request_xml_doc" do
|
|
118
|
-
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
|
119
|
-
inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
|
|
120
|
-
|
|
121
|
-
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
|
122
|
-
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
|
123
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "created a signed logout request" do
|
|
127
|
-
settings.compress_request = true
|
|
128
101
|
|
|
129
102
|
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
|
130
103
|
unauth_url = unauth_req.create(settings)
|
|
131
104
|
|
|
132
105
|
inflated = decode_saml_request_payload(unauth_url)
|
|
133
106
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
|
134
|
-
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
|
135
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
|
136
107
|
end
|
|
137
108
|
|
|
138
|
-
|
|
109
|
+
should "create a signed logout request with 256 digest and signature methods" do
|
|
110
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
139
111
|
settings.compress_request = false
|
|
140
|
-
settings.
|
|
141
|
-
settings.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
|
147
|
-
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
|
148
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha256'/>], request_xml
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it "create a signed logout request with 512 digest and signature method RSA_SHA384" do
|
|
152
|
-
settings.compress_request = false
|
|
153
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
|
|
112
|
+
settings.idp_slo_target_url = "http://example.com?field=value"
|
|
113
|
+
settings.name_identifier_value = "f00f00"
|
|
114
|
+
# sign the logout request
|
|
115
|
+
settings.security[:logout_requests_signed] = true
|
|
116
|
+
settings.security[:embed_sign] = true
|
|
117
|
+
settings.security[:signature_method] = XMLSecurity::Document::SHA256
|
|
154
118
|
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
|
119
|
+
settings.certificate = ruby_saml_cert_text
|
|
120
|
+
settings.private_key = ruby_saml_key_text
|
|
155
121
|
|
|
156
122
|
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
|
|
157
123
|
request_xml = Base64.decode64(params["SAMLRequest"])
|
|
158
124
|
|
|
159
125
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
|
160
|
-
|
|
161
|
-
|
|
126
|
+
request_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/
|
|
127
|
+
request_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha512'\/>/
|
|
162
128
|
end
|
|
163
129
|
end
|
|
164
130
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
131
|
+
context "when the settings indicate to sign the logout request" do
|
|
132
|
+
should "create a signature parameter" do
|
|
133
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
134
|
+
settings.compress_request = false
|
|
135
|
+
settings.idp_slo_target_url = "http://example.com?field=value"
|
|
136
|
+
settings.name_identifier_value = "f00f00"
|
|
171
137
|
settings.security[:logout_requests_signed] = true
|
|
172
138
|
settings.security[:embed_sign] = false
|
|
173
|
-
settings.
|
|
139
|
+
settings.security[:signature_method] = XMLSecurity::Document::SHA1
|
|
140
|
+
settings.certificate = ruby_saml_cert_text
|
|
174
141
|
settings.private_key = ruby_saml_key_text
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do
|
|
178
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
|
179
|
-
|
|
180
|
-
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
181
|
-
assert params['SAMLRequest']
|
|
182
|
-
assert params[:RelayState]
|
|
183
|
-
assert params['Signature']
|
|
184
|
-
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
|
|
185
|
-
|
|
186
|
-
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
|
187
|
-
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
|
188
|
-
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
189
142
|
|
|
190
|
-
|
|
191
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
|
|
192
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do
|
|
196
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
|
197
|
-
|
|
198
|
-
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
143
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
|
|
199
144
|
assert params['Signature']
|
|
200
|
-
|
|
145
|
+
assert params['SigAlg'] == XMLSecurity::Document::SHA1
|
|
201
146
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
|
207
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
|
208
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
|
|
212
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
|
|
213
|
-
|
|
214
|
-
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
147
|
+
# signature_method only affects the embedeed signature
|
|
148
|
+
settings.security[:signature_method] = XMLSecurity::Document::SHA256
|
|
149
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
|
|
215
150
|
assert params['Signature']
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
|
219
|
-
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
|
220
|
-
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
221
|
-
|
|
222
|
-
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
|
223
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA384
|
|
224
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
|
151
|
+
assert params['SigAlg'] == XMLSecurity::Document::SHA1
|
|
225
152
|
end
|
|
153
|
+
end
|
|
226
154
|
|
|
227
|
-
|
|
228
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
|
|
229
|
-
|
|
230
|
-
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
231
|
-
assert params['Signature']
|
|
232
|
-
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
|
|
233
|
-
|
|
234
|
-
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
|
235
|
-
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
|
236
|
-
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
155
|
+
end
|
|
237
156
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
end
|
|
242
|
-
end
|
|
157
|
+
def decode_saml_request_payload(unauth_url)
|
|
158
|
+
payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
|
|
159
|
+
decoded = Base64.decode64(payload)
|
|
243
160
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
logoutrequest.uuid = "new_uuid"
|
|
250
|
-
assert_equal logoutrequest.request_id, logoutrequest.uuid
|
|
251
|
-
assert_equal "new_uuid", logoutrequest.request_id
|
|
252
|
-
end
|
|
253
|
-
end
|
|
161
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
162
|
+
inflated = zstream.inflate(decoded)
|
|
163
|
+
zstream.finish
|
|
164
|
+
zstream.close
|
|
165
|
+
inflated
|
|
254
166
|
end
|
|
167
|
+
|
|
255
168
|
end
|
data/test/logoutresponse_test.rb
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
|
2
|
-
require
|
|
2
|
+
require 'rexml/document'
|
|
3
|
+
require 'responses/logoutresponse_fixtures'
|
|
4
|
+
class RubySamlTest < Test::Unit::TestCase
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
describe "#new" do
|
|
9
|
-
it "raise an exception when response is initialized with nil" do
|
|
6
|
+
context "Logoutresponse" do
|
|
7
|
+
context "#new" do
|
|
8
|
+
should "raise an exception when response is initialized with nil" do
|
|
10
9
|
assert_raises(ArgumentError) { OneLogin::RubySaml::Logoutresponse.new(nil) }
|
|
11
10
|
end
|
|
12
|
-
|
|
11
|
+
should "default to empty settings" do
|
|
13
12
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new( valid_response)
|
|
14
|
-
|
|
13
|
+
assert_nil logoutresponse.settings
|
|
15
14
|
end
|
|
16
|
-
|
|
15
|
+
should "accept constructor-injected settings" do
|
|
17
16
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings)
|
|
18
|
-
|
|
17
|
+
assert_not_nil logoutresponse.settings
|
|
19
18
|
end
|
|
20
|
-
|
|
19
|
+
should "accept constructor-injected options" do
|
|
21
20
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, nil, { :foo => :bar} )
|
|
22
21
|
assert !logoutresponse.options.empty?
|
|
23
22
|
end
|
|
24
|
-
|
|
23
|
+
should "support base64 encoded responses" do
|
|
25
24
|
expected_response = valid_response
|
|
26
25
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(Base64.encode64(expected_response), settings)
|
|
27
26
|
|
|
@@ -29,21 +28,21 @@ class LogoutResponseTest < Minitest::Test
|
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
context "#validate" do
|
|
32
|
+
should "validate the response" do
|
|
34
33
|
in_relation_to_request_id = random_id
|
|
35
|
-
|
|
36
|
-
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:
|
|
34
|
+
|
|
35
|
+
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
|
|
37
36
|
|
|
38
37
|
assert logoutresponse.validate
|
|
39
38
|
|
|
40
|
-
assert_equal settings.
|
|
39
|
+
assert_equal settings.issuer, logoutresponse.issuer
|
|
41
40
|
assert_equal in_relation_to_request_id, logoutresponse.in_response_to
|
|
42
41
|
|
|
43
42
|
assert logoutresponse.success?
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
should "invalidate responses with wrong id when given option :matches_uuid" do
|
|
47
46
|
|
|
48
47
|
expected_request_id = "_some_other_expected_uuid"
|
|
49
48
|
opts = { :matches_request_id => expected_request_id}
|
|
@@ -51,10 +50,10 @@ class LogoutResponseTest < Minitest::Test
|
|
|
51
50
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings, opts)
|
|
52
51
|
|
|
53
52
|
assert !logoutresponse.validate
|
|
54
|
-
|
|
53
|
+
assert_not_equal expected_request_id, logoutresponse.in_response_to
|
|
55
54
|
end
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
should "invalidate responses with wrong request status" do
|
|
58
57
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, settings)
|
|
59
58
|
|
|
60
59
|
assert !logoutresponse.validate
|
|
@@ -62,8 +61,8 @@ class LogoutResponseTest < Minitest::Test
|
|
|
62
61
|
end
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
context "#validate!" do
|
|
65
|
+
should "validates good responses" do
|
|
67
66
|
in_relation_to_request_id = random_id
|
|
68
67
|
|
|
69
68
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
|
|
@@ -71,7 +70,7 @@ class LogoutResponseTest < Minitest::Test
|
|
|
71
70
|
logoutresponse.validate!
|
|
72
71
|
end
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
should "raises validation error when matching for wrong request id" do
|
|
75
74
|
|
|
76
75
|
expected_request_id = "_some_other_expected_id"
|
|
77
76
|
opts = { :matches_request_id => expected_request_id}
|
|
@@ -81,25 +80,37 @@ class LogoutResponseTest < Minitest::Test
|
|
|
81
80
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! }
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
should "raise validation error for wrong request status" do
|
|
85
84
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, settings)
|
|
86
85
|
|
|
87
86
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! }
|
|
88
87
|
end
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
should "raise validation error when in bad state" do
|
|
90
|
+
# no settings
|
|
91
|
+
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response)
|
|
92
|
+
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! }
|
|
93
|
+
end
|
|
92
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::RubySaml::Logoutresponse.new(unsuccessful_response, bad_settings)
|
|
93
99
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! }
|
|
94
100
|
end
|
|
95
|
-
end
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
should "raise error for invalid xml" do
|
|
103
|
+
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_response, settings)
|
|
104
|
+
|
|
105
|
+
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! }
|
|
101
106
|
end
|
|
102
107
|
end
|
|
103
108
|
|
|
104
109
|
end
|
|
110
|
+
|
|
111
|
+
# logoutresponse fixtures
|
|
112
|
+
def random_id
|
|
113
|
+
"_#{UUID.new.generate}"
|
|
114
|
+
end
|
|
115
|
+
|
|
105
116
|
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
|
2
|
+
|
|
3
|
+
class MetadataTest < Test::Unit::TestCase
|
|
4
|
+
|
|
5
|
+
def setup
|
|
6
|
+
@settings = OneLogin::RubySaml::Settings.new
|
|
7
|
+
@settings.issuer = "https://example.com"
|
|
8
|
+
@settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
9
|
+
@settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
|
10
|
+
@settings.security[:authn_requests_signed] = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
should "generate Service Provider Metadata with X509Certificate" do
|
|
14
|
+
@settings.security[:authn_requests_signed] = true
|
|
15
|
+
@settings.certificate = ruby_saml_cert_text
|
|
16
|
+
|
|
17
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(@settings)
|
|
18
|
+
|
|
19
|
+
# assert xml_text can be parsed into an xml doc
|
|
20
|
+
xml_doc = REXML::Document.new(xml_text)
|
|
21
|
+
|
|
22
|
+
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
|
|
23
|
+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
|
24
|
+
|
|
25
|
+
cert_node = REXML::XPath.first(xml_doc, "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", {
|
|
26
|
+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
|
27
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
|
28
|
+
})
|
|
29
|
+
cert_text = cert_node.text
|
|
30
|
+
cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
|
|
31
|
+
assert_equal ruby_saml_cert.to_der, cert.to_der
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
should "should generate Service Provider Metadata" do
|
|
35
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
36
|
+
settings.issuer = "https://example.com"
|
|
37
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
38
|
+
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
|
39
|
+
settings.security[:authn_requests_signed] = false
|
|
40
|
+
|
|
41
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
|
|
42
|
+
|
|
43
|
+
# assert correct xml declaration
|
|
44
|
+
start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
|
|
45
|
+
assert xml_text[0..start.length-1] == start
|
|
46
|
+
|
|
47
|
+
# assert xml_text can be parsed into an xml doc
|
|
48
|
+
xml_doc = REXML::Document.new(xml_text)
|
|
49
|
+
|
|
50
|
+
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
|
51
|
+
|
|
52
|
+
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
|
|
53
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
|
54
|
+
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
|
55
|
+
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
|
56
|
+
|
|
57
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
|
58
|
+
|
|
59
|
+
acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
|
|
60
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
|
61
|
+
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
should "generate attribute service if configured" do
|
|
65
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
66
|
+
settings.issuer = "https://example.com"
|
|
67
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
68
|
+
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
|
69
|
+
settings.attribute_consuming_service.configure do
|
|
70
|
+
service_name "Test Service"
|
|
71
|
+
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
|
|
75
|
+
xml_doc = REXML::Document.new(xml_text)
|
|
76
|
+
acs = REXML::XPath.first(xml_doc, "//md:AttributeConsumingService")
|
|
77
|
+
assert_equal "true", acs.attribute("isDefault").value
|
|
78
|
+
assert_equal "1", acs.attribute("index").value
|
|
79
|
+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
|
|
80
|
+
req_attr = REXML::XPath.first(xml_doc, "//md:RequestedAttribute")
|
|
81
|
+
assert_equal "Name", req_attr.attribute("Name").value
|
|
82
|
+
assert_equal "Name Format", req_attr.attribute("NameFormat").value
|
|
83
|
+
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
|
|
84
|
+
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|