kl-ruby-saml 0.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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +14 -0
- data/.travis.yml +17 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/README.md +575 -0
- data/Rakefile +41 -0
- data/changelog.md +75 -0
- data/gemfiles/nokogiri-1.5.gemfile +5 -0
- data/lib/onelogin/ruby-saml.rb +17 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
- data/lib/onelogin/ruby-saml/attributes.rb +128 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +156 -0
- data/lib/onelogin/ruby-saml/http_error.rb +7 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
- data/lib/onelogin/ruby-saml/logging.rb +30 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
- data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
- data/lib/onelogin/ruby-saml/metadata.rb +123 -0
- data/lib/onelogin/ruby-saml/response.rb +722 -0
- data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
- data/lib/onelogin/ruby-saml/settings.rb +165 -0
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
- data/lib/onelogin/ruby-saml/utils.rb +172 -0
- data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
- data/lib/onelogin/ruby-saml/version.rb +5 -0
- data/lib/ruby-saml.rb +1 -0
- data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
- 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 +337 -0
- data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
- 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 +136 -0
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/xmldsig-core-schema.xsd +309 -0
- data/lib/xml_security.rb +358 -0
- data/ruby-saml.gemspec +57 -0
- data/test/certificates/certificate1 +12 -0
- data/test/certificates/certificate_without_head_foot +1 -0
- data/test/certificates/formatted_certificate +14 -0
- data/test/certificates/formatted_private_key +12 -0
- data/test/certificates/formatted_rsa_private_key +12 -0
- data/test/certificates/invalid_certificate1 +1 -0
- data/test/certificates/invalid_certificate2 +1 -0
- data/test/certificates/invalid_certificate3 +12 -0
- data/test/certificates/invalid_private_key1 +1 -0
- data/test/certificates/invalid_private_key2 +1 -0
- data/test/certificates/invalid_private_key3 +10 -0
- data/test/certificates/invalid_rsa_private_key1 +1 -0
- data/test/certificates/invalid_rsa_private_key2 +1 -0
- data/test/certificates/invalid_rsa_private_key3 +10 -0
- data/test/certificates/ruby-saml.crt +14 -0
- data/test/certificates/ruby-saml.key +15 -0
- data/test/idp_metadata_parser_test.rb +95 -0
- data/test/logging_test.rb +62 -0
- data/test/logout_requests/invalid_slo_request.xml +6 -0
- data/test/logout_requests/slo_request.xml +4 -0
- data/test/logout_requests/slo_request.xml.base64 +1 -0
- data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
- data/test/logout_requests/slo_request_with_session_index.xml +5 -0
- data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
- data/test/logoutrequest_test.rb +211 -0
- data/test/logoutresponse_test.rb +258 -0
- data/test/metadata_test.rb +203 -0
- data/test/request_test.rb +282 -0
- data/test/response_test.rb +1094 -0
- data/test/responses/adfs_response_sha1.xml +46 -0
- data/test/responses/adfs_response_sha256.xml +46 -0
- data/test/responses/adfs_response_sha384.xml +46 -0
- data/test/responses/adfs_response_sha512.xml +46 -0
- data/test/responses/adfs_response_xmlns.xml +45 -0
- data/test/responses/attackxee.xml +13 -0
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
- data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
- data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
- data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
- data/test/responses/invalids/no_id.xml.base64 +1 -0
- data/test/responses/invalids/no_saml2.xml.base64 +1 -0
- data/test/responses/invalids/no_signature.xml.base64 +1 -0
- data/test/responses/invalids/no_status.xml.base64 +1 -0
- data/test/responses/invalids/no_status_code.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
- data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
- data/test/responses/no_signature_ns.xml +48 -0
- data/test/responses/open_saml_response.xml +56 -0
- data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
- data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
- data/test/responses/response_eval.xml +7 -0
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_unsigned_xml_base64 +1 -0
- data/test/responses/response_with_ampersands.xml +139 -0
- data/test/responses/response_with_ampersands.xml.base64 +93 -0
- data/test/responses/response_with_multiple_attribute_values.xml +67 -0
- data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
- data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
- data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
- data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
- data/test/responses/response_without_attributes.xml.base64 +79 -0
- data/test/responses/response_wrapped.xml.base64 +150 -0
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/simple_saml_php.xml +71 -0
- data/test/responses/starfield_response.xml.base64 +1 -0
- data/test/responses/test_sign.xml +43 -0
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/valid_response.xml.base64 +1 -0
- data/test/saml_message_test.rb +56 -0
- data/test/settings_test.rb +218 -0
- data/test/slo_logoutrequest_test.rb +275 -0
- data/test/slo_logoutresponse_test.rb +185 -0
- data/test/test_helper.rb +252 -0
- data/test/utils_test.rb +145 -0
- data/test/xml_security_test.rb +329 -0
- metadata +415 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
|
2
|
+
|
|
3
|
+
require 'onelogin/ruby-saml/metadata'
|
|
4
|
+
|
|
5
|
+
class MetadataTest < Minitest::Test
|
|
6
|
+
|
|
7
|
+
describe 'Metadata' do
|
|
8
|
+
let(:settings) { OneLogin::RubySaml::Settings.new }
|
|
9
|
+
let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings, false) }
|
|
10
|
+
let(:xml_doc) { REXML::Document.new(xml_text) }
|
|
11
|
+
let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") }
|
|
12
|
+
let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
settings.issuer = "https://example.com"
|
|
16
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
17
|
+
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "generates Pretty Print Service Provider Metadata" do
|
|
21
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings, true)
|
|
22
|
+
# assert correct xml declaration
|
|
23
|
+
start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
|
|
24
|
+
assert_equal xml_text[0..start.length-1],start
|
|
25
|
+
|
|
26
|
+
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
|
27
|
+
|
|
28
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
|
29
|
+
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
|
30
|
+
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
|
31
|
+
|
|
32
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
|
33
|
+
|
|
34
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
|
35
|
+
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
|
36
|
+
|
|
37
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "generates Service Provider Metadata" do
|
|
41
|
+
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
42
|
+
settings.single_logout_service_url = "https://foo.example/saml/sls"
|
|
43
|
+
xml_metadata = OneLogin::RubySaml::Metadata.new.generate(settings, false)
|
|
44
|
+
|
|
45
|
+
start = "<?xml version='1.0' encoding='UTF-8'?><md:EntityDescriptor"
|
|
46
|
+
assert_equal xml_metadata[0..start.length-1],start
|
|
47
|
+
|
|
48
|
+
doc_metadata = REXML::Document.new(xml_metadata)
|
|
49
|
+
sls = REXML::XPath.first(doc_metadata, "//md:SingleLogoutService")
|
|
50
|
+
|
|
51
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", sls.attribute("Binding").value
|
|
52
|
+
assert_equal "https://foo.example/saml/sls", sls.attribute("Location").value
|
|
53
|
+
assert_equal "https://foo.example/saml/sls", sls.attribute("ResponseLocation").value
|
|
54
|
+
assert_nil sls.attribute("isDefault")
|
|
55
|
+
assert_nil sls.attribute("index")
|
|
56
|
+
|
|
57
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "generates Service Provider Metadata with single logout service" do
|
|
61
|
+
start = "<?xml version='1.0' encoding='UTF-8'?><md:EntityDescriptor"
|
|
62
|
+
assert_equal xml_text[0..start.length-1], start
|
|
63
|
+
|
|
64
|
+
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
|
65
|
+
|
|
66
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
|
67
|
+
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
|
68
|
+
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
|
69
|
+
|
|
70
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
|
71
|
+
|
|
72
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
|
73
|
+
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
|
74
|
+
|
|
75
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "when auth requests are signed" do
|
|
79
|
+
let(:key_descriptors) do
|
|
80
|
+
REXML::XPath.match(
|
|
81
|
+
xml_doc,
|
|
82
|
+
"//md:KeyDescriptor",
|
|
83
|
+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
let(:cert_nodes) do
|
|
87
|
+
REXML::XPath.match(
|
|
88
|
+
xml_doc,
|
|
89
|
+
"//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
|
90
|
+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
|
91
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) }
|
|
95
|
+
|
|
96
|
+
before do
|
|
97
|
+
settings.certificate = ruby_saml_cert_text
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "generates Service Provider Metadata with AuthnRequestsSigned" do
|
|
101
|
+
settings.security[:authn_requests_signed] = true
|
|
102
|
+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
|
103
|
+
assert_equal ruby_saml_cert.to_der, cert.to_der
|
|
104
|
+
|
|
105
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "generates Service Provider Metadata with X509Certificate for sign and encrypt" do
|
|
109
|
+
assert_equal 2, key_descriptors.length
|
|
110
|
+
assert_equal "signing", key_descriptors[0].attribute("use").value
|
|
111
|
+
assert_equal "encryption", key_descriptors[1].attribute("use").value
|
|
112
|
+
|
|
113
|
+
assert_equal 2, cert_nodes.length
|
|
114
|
+
assert_equal ruby_saml_cert.to_der, cert.to_der
|
|
115
|
+
assert_equal cert_nodes[0].text, cert_nodes[1].text
|
|
116
|
+
|
|
117
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "when attribute service is configured" do
|
|
122
|
+
let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
|
|
123
|
+
let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }
|
|
124
|
+
|
|
125
|
+
before do
|
|
126
|
+
settings.attribute_consuming_service.configure do
|
|
127
|
+
service_name "Test Service"
|
|
128
|
+
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "generates attribute service" do
|
|
133
|
+
assert_equal "true", attr_svc.attribute("isDefault").value
|
|
134
|
+
assert_equal "1", attr_svc.attribute("index").value
|
|
135
|
+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
|
|
136
|
+
|
|
137
|
+
assert_equal "Name", req_attr.attribute("Name").value
|
|
138
|
+
assert_equal "Name Format", req_attr.attribute("NameFormat").value
|
|
139
|
+
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
|
|
140
|
+
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//saml:AttributeValue").text.strip
|
|
141
|
+
|
|
142
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe "#service_name" do
|
|
146
|
+
before do
|
|
147
|
+
settings.attribute_consuming_service.service_name("Test2 Service")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "change service name" do
|
|
151
|
+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test2 Service"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe "#service_index" do
|
|
156
|
+
before do
|
|
157
|
+
settings.attribute_consuming_service.service_index(2)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "change service index" do
|
|
161
|
+
assert_equal "2", attr_svc.attribute("index").value
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "when the settings indicate to sign (embedded) metadata" do
|
|
167
|
+
before do
|
|
168
|
+
settings.security[:metadata_signed] = true
|
|
169
|
+
settings.certificate = ruby_saml_cert_text
|
|
170
|
+
settings.private_key = ruby_saml_key_text
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "creates a signed metadata" do
|
|
174
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
|
175
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
|
|
176
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], xml_text
|
|
177
|
+
signed_metadata = XMLSecurity::SignedDocument.new(xml_text)
|
|
178
|
+
assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false)
|
|
179
|
+
|
|
180
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
describe "when digest and signature methods are specified" do
|
|
184
|
+
before do
|
|
185
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
|
186
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "creates a signed metadata with specified digest and signature methods" do
|
|
190
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
|
191
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
|
|
192
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], xml_text
|
|
193
|
+
|
|
194
|
+
signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text)
|
|
195
|
+
|
|
196
|
+
assert signed_metadata_2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
197
|
+
|
|
198
|
+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
|
2
|
+
|
|
3
|
+
require 'onelogin/ruby-saml/authrequest'
|
|
4
|
+
|
|
5
|
+
class RequestTest < Minitest::Test
|
|
6
|
+
|
|
7
|
+
describe "Authrequest" do
|
|
8
|
+
let(:settings) { OneLogin::RubySaml::Settings.new }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
settings.idp_sso_target_url = "http://example.com"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "create the deflated SAMLRequest URL parameter" do
|
|
15
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
16
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
17
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
18
|
+
decoded = Base64.decode64(payload)
|
|
19
|
+
|
|
20
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
21
|
+
inflated = zstream.inflate(decoded)
|
|
22
|
+
zstream.finish
|
|
23
|
+
zstream.close
|
|
24
|
+
|
|
25
|
+
assert_match /^<samlp:AuthnRequest/, inflated
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "create the deflated SAMLRequest URL parameter including the Destination" do
|
|
29
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
30
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
31
|
+
decoded = Base64.decode64(payload)
|
|
32
|
+
|
|
33
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
34
|
+
inflated = zstream.inflate(decoded)
|
|
35
|
+
zstream.finish
|
|
36
|
+
zstream.close
|
|
37
|
+
|
|
38
|
+
assert_match /<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "create the SAMLRequest URL parameter without deflating" do
|
|
42
|
+
settings.compress_request = false
|
|
43
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
44
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
45
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
46
|
+
decoded = Base64.decode64(payload)
|
|
47
|
+
|
|
48
|
+
assert_match /^<samlp:AuthnRequest/, decoded
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "create the SAMLRequest URL parameter with IsPassive" do
|
|
52
|
+
settings.passive = true
|
|
53
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
54
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
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
|
+
it "create the SAMLRequest URL parameter with ProtocolBinding" do
|
|
67
|
+
settings.protocol_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
|
68
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
69
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
70
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
71
|
+
decoded = Base64.decode64(payload)
|
|
72
|
+
|
|
73
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
74
|
+
inflated = zstream.inflate(decoded)
|
|
75
|
+
zstream.finish
|
|
76
|
+
zstream.close
|
|
77
|
+
|
|
78
|
+
assert_match /<samlp:AuthnRequest[^<]* ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'/, inflated
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "create the SAMLRequest URL parameter with AttributeConsumingServiceIndex" do
|
|
82
|
+
settings.attributes_index = 30
|
|
83
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
84
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
85
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
86
|
+
decoded = Base64.decode64(payload)
|
|
87
|
+
|
|
88
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
89
|
+
inflated = zstream.inflate(decoded)
|
|
90
|
+
zstream.finish
|
|
91
|
+
zstream.close
|
|
92
|
+
assert_match /<samlp:AuthnRequest[^<]* AttributeConsumingServiceIndex='30'/, inflated
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "create the SAMLRequest URL parameter with ForceAuthn" do
|
|
96
|
+
settings.force_authn = true
|
|
97
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
98
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
99
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
100
|
+
decoded = Base64.decode64(payload)
|
|
101
|
+
|
|
102
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
103
|
+
inflated = zstream.inflate(decoded)
|
|
104
|
+
zstream.finish
|
|
105
|
+
zstream.close
|
|
106
|
+
assert_match /<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "create the SAMLRequest URL parameter with NameID Format" do
|
|
110
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
|
111
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
112
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
|
113
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
|
114
|
+
decoded = Base64.decode64(payload)
|
|
115
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
116
|
+
inflated = zstream.inflate(decoded)
|
|
117
|
+
zstream.finish
|
|
118
|
+
zstream.close
|
|
119
|
+
|
|
120
|
+
assert_match /<samlp:NameIDPolicy[^<]* AllowCreate='true'/, inflated
|
|
121
|
+
assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "accept extra parameters" do
|
|
125
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
|
|
126
|
+
assert_match /&hello=there$/, auth_url
|
|
127
|
+
|
|
128
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => nil })
|
|
129
|
+
assert_match /&hello=$/, auth_url
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe "when the target url doesn't contain a query string" do
|
|
133
|
+
it "create the SAMLRequest parameter correctly" do
|
|
134
|
+
|
|
135
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
136
|
+
assert_match /^http:\/\/example.com\?SAMLRequest/, auth_url
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "when the target url contains a query string" do
|
|
141
|
+
it "create the SAMLRequest parameter correctly" do
|
|
142
|
+
settings.idp_sso_target_url = "http://example.com?field=value"
|
|
143
|
+
|
|
144
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
|
145
|
+
assert_match /^http:\/\/example.com\?field=value&SAMLRequest/, auth_url
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "create the saml:AuthnContextClassRef element correctly" do
|
|
150
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
151
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
152
|
+
assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "create the saml:AuthnContextClassRef with comparison exact" do
|
|
156
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
157
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
158
|
+
assert_match /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/, auth_doc.to_s
|
|
159
|
+
assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "create the saml:AuthnContextClassRef with comparison minimun" do
|
|
163
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
164
|
+
settings.authn_context_comparison = 'minimun'
|
|
165
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
166
|
+
assert_match /<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/, auth_doc.to_s
|
|
167
|
+
assert_match /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "create the saml:AuthnContextDeclRef element correctly" do
|
|
171
|
+
settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
|
172
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
173
|
+
assert_match /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe "#create_params when the settings indicate to sign (embebed) the request" do
|
|
177
|
+
before do
|
|
178
|
+
settings.compress_request = false
|
|
179
|
+
settings.idp_sso_target_url = "http://example.com?field=value"
|
|
180
|
+
settings.security[:authn_requests_signed] = true
|
|
181
|
+
settings.security[:embed_sign] = true
|
|
182
|
+
settings.certificate = ruby_saml_cert_text
|
|
183
|
+
settings.private_key = ruby_saml_key_text
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "create a signed request" do
|
|
187
|
+
params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
|
|
188
|
+
request_xml = Base64.decode64(params["SAMLRequest"])
|
|
189
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
|
190
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], request_xml
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "create a signed request with 256 digest and signature methods" do
|
|
194
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
|
195
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
|
196
|
+
|
|
197
|
+
params = OneLogin::RubySaml::Authrequest.new.create_params(settings)
|
|
198
|
+
|
|
199
|
+
request_xml = Base64.decode64(params["SAMLRequest"])
|
|
200
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
|
201
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
|
202
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], request_xml
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe "#create_params when the settings indicate to sign the request" do
|
|
207
|
+
let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
|
|
208
|
+
|
|
209
|
+
before do
|
|
210
|
+
settings.compress_request = false
|
|
211
|
+
settings.idp_sso_target_url = "http://example.com?field=value"
|
|
212
|
+
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
|
|
213
|
+
settings.security[:authn_requests_signed] = true
|
|
214
|
+
settings.security[:embed_sign] = false
|
|
215
|
+
settings.certificate = ruby_saml_cert_text
|
|
216
|
+
settings.private_key = ruby_saml_key_text
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "create a signature parameter with RSA_SHA1 and validate it" do
|
|
220
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
|
221
|
+
|
|
222
|
+
params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
223
|
+
assert params['SAMLRequest']
|
|
224
|
+
assert params[:RelayState]
|
|
225
|
+
assert params['Signature']
|
|
226
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
|
|
227
|
+
|
|
228
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
|
229
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
|
230
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
231
|
+
|
|
232
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
|
233
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
|
|
234
|
+
|
|
235
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "create a signature parameter with RSA_SHA256 and validate it" do
|
|
239
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
|
240
|
+
|
|
241
|
+
params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
|
242
|
+
assert params['Signature']
|
|
243
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
|
|
244
|
+
|
|
245
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
|
246
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
|
247
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
248
|
+
|
|
249
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
|
250
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
|
251
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "create the saml:AuthnContextClassRef element correctly" do
|
|
256
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
257
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
258
|
+
assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "create the saml:AuthnContextClassRef with comparison exact" do
|
|
262
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
263
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
264
|
+
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/
|
|
265
|
+
assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "create the saml:AuthnContextClassRef with comparison minimun" do
|
|
269
|
+
settings.authn_context = 'secure/name/password/uri'
|
|
270
|
+
settings.authn_context_comparison = 'minimun'
|
|
271
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
272
|
+
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/
|
|
273
|
+
assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "create the saml:AuthnContextDeclRef element correctly" do
|
|
277
|
+
settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
|
278
|
+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
279
|
+
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|