ruby-saml 1.9.0 → 1.12.0
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 +5 -5
- data/.travis.yml +30 -14
- data/README.md +108 -22
- data/changelog.md +38 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +23 -6
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +239 -171
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +20 -5
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -9
- data/lib/onelogin/ruby-saml/metadata.rb +11 -3
- data/lib/onelogin/ruby-saml/response.rb +67 -21
- data/lib/onelogin/ruby-saml/saml_message.rb +12 -2
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +73 -7
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +20 -1
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +38 -16
- data/lib/onelogin/ruby-saml/utils.rb +74 -1
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +34 -6
- data/ruby-saml.gemspec +15 -7
- metadata +36 -278
- data/test/certificates/certificate1 +0 -12
- data/test/certificates/certificate_without_head_foot +0 -1
- 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/certificates/ruby-saml.crt +0 -14
- data/test/certificates/ruby-saml.key +0 -15
- data/test/idp_metadata_parser_test.rb +0 -579
- data/test/logging_test.rb +0 -62
- data/test/logout_requests/invalid_slo_request.xml +0 -6
- data/test/logout_requests/slo_request.xml +0 -4
- data/test/logout_requests/slo_request.xml.base64 +0 -1
- data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
- data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
- data/test/logout_requests/slo_request_with_session_index.xml +0 -5
- data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
- data/test/logoutrequest_test.rb +0 -226
- data/test/logoutresponse_test.rb +0 -402
- data/test/metadata/idp_descriptor.xml +0 -26
- data/test/metadata/idp_descriptor_2.xml +0 -56
- data/test/metadata/idp_descriptor_3.xml +0 -14
- data/test/metadata/idp_descriptor_4.xml +0 -72
- data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
- data/test/metadata/idp_metadata_multi_certs.xml +0 -75
- data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
- data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
- data/test/metadata/idp_multiple_descriptors.xml +0 -53
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -323
- data/test/response_test.rb +0 -1619
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.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/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.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/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- 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_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -329
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -199
- data/test/test_helper.rb +0 -327
- data/test/utils_test.rb +0 -254
- data/test/xml_security_test.rb +0 -421
|
@@ -8,9 +8,9 @@ module OneLogin
|
|
|
8
8
|
|
|
9
9
|
def self.logger
|
|
10
10
|
@logger ||= begin
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
(defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
|
12
|
+
DEFAULT_LOGGER
|
|
13
|
+
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.logger=(logger)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "onelogin/ruby-saml/logging"
|
|
2
2
|
require "onelogin/ruby-saml/saml_message"
|
|
3
3
|
require "onelogin/ruby-saml/utils"
|
|
4
|
+
require "onelogin/ruby-saml/setting_error"
|
|
4
5
|
|
|
5
6
|
# Only supports SAML 2.0
|
|
6
7
|
module OneLogin
|
|
@@ -20,6 +21,10 @@ module OneLogin
|
|
|
20
21
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
def request_id
|
|
25
|
+
@uuid
|
|
26
|
+
end
|
|
27
|
+
|
|
23
28
|
# Creates the Logout Request string.
|
|
24
29
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
25
30
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
@@ -33,6 +38,7 @@ module OneLogin
|
|
|
33
38
|
params.each_pair do |key, value|
|
|
34
39
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
35
40
|
end
|
|
41
|
+
raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
|
36
42
|
@logout_url = settings.idp_slo_target_url + request_params
|
|
37
43
|
end
|
|
38
44
|
|
|
@@ -89,6 +95,11 @@ module OneLogin
|
|
|
89
95
|
# @return [String] The SAMLRequest String.
|
|
90
96
|
#
|
|
91
97
|
def create_logout_request_xml_doc(settings)
|
|
98
|
+
document = create_xml_document(settings)
|
|
99
|
+
sign_document(document, settings)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def create_xml_document(settings)
|
|
92
103
|
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
93
104
|
|
|
94
105
|
request_doc = XMLSecurity::Document.new
|
|
@@ -98,11 +109,11 @@ module OneLogin
|
|
|
98
109
|
root.attributes['ID'] = uuid
|
|
99
110
|
root.attributes['IssueInstant'] = time
|
|
100
111
|
root.attributes['Version'] = "2.0"
|
|
101
|
-
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
112
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
|
102
113
|
|
|
103
|
-
if settings.
|
|
114
|
+
if settings.sp_entity_id
|
|
104
115
|
issuer = root.add_element "saml:Issuer"
|
|
105
|
-
issuer.text = settings.
|
|
116
|
+
issuer.text = settings.sp_entity_id
|
|
106
117
|
end
|
|
107
118
|
|
|
108
119
|
nameid = root.add_element "saml:NameID"
|
|
@@ -122,14 +133,18 @@ module OneLogin
|
|
|
122
133
|
sessionindex.text = settings.sessionindex
|
|
123
134
|
end
|
|
124
135
|
|
|
136
|
+
request_doc
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def sign_document(document, settings)
|
|
125
140
|
# embed signature
|
|
126
141
|
if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
|
127
142
|
private_key = settings.get_sp_key
|
|
128
143
|
cert = settings.get_sp_cert
|
|
129
|
-
|
|
144
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
130
145
|
end
|
|
131
146
|
|
|
132
|
-
|
|
147
|
+
document
|
|
133
148
|
end
|
|
134
149
|
end
|
|
135
150
|
end
|
|
@@ -47,15 +47,16 @@ module OneLogin
|
|
|
47
47
|
@document = XMLSecurity::SignedDocument.new(@response)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def response_id
|
|
51
|
+
id(document)
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
# Checks if the Status has the "Success" code
|
|
51
55
|
# @return [Boolean] True if the StatusCode is Sucess
|
|
52
56
|
# @raise [ValidationError] if soft == false and validation fails
|
|
53
57
|
#
|
|
54
58
|
def success?
|
|
55
|
-
|
|
56
|
-
return append_error("Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <#@status_code>")
|
|
57
|
-
end
|
|
58
|
-
true
|
|
59
|
+
return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
# @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
|
|
@@ -65,7 +66,7 @@ module OneLogin
|
|
|
65
66
|
node = REXML::XPath.first(
|
|
66
67
|
document,
|
|
67
68
|
"/p:LogoutResponse",
|
|
68
|
-
{ "p" => PROTOCOL
|
|
69
|
+
{ "p" => PROTOCOL }
|
|
69
70
|
)
|
|
70
71
|
node.nil? ? nil : node.attributes['InResponseTo']
|
|
71
72
|
end
|
|
@@ -88,7 +89,7 @@ module OneLogin
|
|
|
88
89
|
#
|
|
89
90
|
def status_code
|
|
90
91
|
@status_code ||= begin
|
|
91
|
-
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL
|
|
92
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL })
|
|
92
93
|
node.nil? ? nil : node.attributes["Value"]
|
|
93
94
|
end
|
|
94
95
|
end
|
|
@@ -98,7 +99,7 @@ module OneLogin
|
|
|
98
99
|
node = REXML::XPath.first(
|
|
99
100
|
document,
|
|
100
101
|
"/p:LogoutResponse/p:Status/p:StatusMessage",
|
|
101
|
-
{ "p" => PROTOCOL
|
|
102
|
+
{ "p" => PROTOCOL }
|
|
102
103
|
)
|
|
103
104
|
Utils.element_text(node)
|
|
104
105
|
end
|
|
@@ -166,7 +167,7 @@ module OneLogin
|
|
|
166
167
|
|
|
167
168
|
return append_error("No settings on logout response") if settings.nil?
|
|
168
169
|
|
|
169
|
-
return append_error("No
|
|
170
|
+
return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil?
|
|
170
171
|
|
|
171
172
|
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
|
|
172
173
|
return append_error("No fingerprint or certificate on settings of the logout response")
|
|
@@ -231,13 +232,19 @@ module OneLogin
|
|
|
231
232
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
|
232
233
|
)
|
|
233
234
|
|
|
235
|
+
expired = false
|
|
234
236
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
235
237
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
|
236
|
-
:cert =>
|
|
238
|
+
:cert => idp_cert,
|
|
237
239
|
:sig_alg => options[:get_params]['SigAlg'],
|
|
238
240
|
:signature => options[:get_params]['Signature'],
|
|
239
241
|
:query_string => query_string
|
|
240
242
|
)
|
|
243
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
|
244
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
245
|
+
expired = true
|
|
246
|
+
end
|
|
247
|
+
end
|
|
241
248
|
else
|
|
242
249
|
valid = false
|
|
243
250
|
idp_certs[:signing].each do |signing_idp_cert|
|
|
@@ -248,11 +255,20 @@ module OneLogin
|
|
|
248
255
|
:query_string => query_string
|
|
249
256
|
)
|
|
250
257
|
if valid
|
|
258
|
+
if settings.security[:check_idp_cert_expiration]
|
|
259
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
|
260
|
+
expired = true
|
|
261
|
+
end
|
|
262
|
+
end
|
|
251
263
|
break
|
|
252
264
|
end
|
|
253
265
|
end
|
|
254
266
|
end
|
|
255
267
|
|
|
268
|
+
if expired
|
|
269
|
+
error_msg = "IdP x509 certificate expired"
|
|
270
|
+
return append_error(error_msg)
|
|
271
|
+
end
|
|
256
272
|
unless valid
|
|
257
273
|
error_msg = "Invalid Signature on Logout Response"
|
|
258
274
|
return append_error(error_msg)
|
|
@@ -15,9 +15,11 @@ module OneLogin
|
|
|
15
15
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
16
16
|
# @param pretty_print [Boolean] Pretty print or not the response
|
|
17
17
|
# (No pretty print if you gonna validate the signature)
|
|
18
|
+
# @param valid_until [DateTime] Metadata's valid time
|
|
19
|
+
# @param cache_duration [Integer] Duration of the cache in seconds
|
|
18
20
|
# @return [String] XML Metadata of the Service Provider
|
|
19
21
|
#
|
|
20
|
-
def generate(settings, pretty_print=false)
|
|
22
|
+
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
|
|
21
23
|
meta_doc = XMLSecurity::Document.new
|
|
22
24
|
namespaces = {
|
|
23
25
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
@@ -57,8 +59,14 @@ module OneLogin
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
|
60
|
-
if settings.
|
|
61
|
-
root.attributes["entityID"] = settings.
|
|
62
|
+
if settings.sp_entity_id
|
|
63
|
+
root.attributes["entityID"] = settings.sp_entity_id
|
|
64
|
+
end
|
|
65
|
+
if valid_until
|
|
66
|
+
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z')
|
|
67
|
+
end
|
|
68
|
+
if cache_duration
|
|
69
|
+
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S"
|
|
62
70
|
end
|
|
63
71
|
if settings.single_logout_service_url
|
|
64
72
|
sp_sso.add_element "md:SingleLogoutService", {
|
|
@@ -34,7 +34,7 @@ module OneLogin
|
|
|
34
34
|
# This is not a whitelist to allow people extending OneLogin::RubySaml:Response
|
|
35
35
|
# and pass custom options
|
|
36
36
|
AVAILABLE_OPTIONS = [
|
|
37
|
-
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
|
|
37
|
+
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
|
|
38
38
|
:skip_destination, :skip_recipient_check, :skip_subject_confirmation
|
|
39
39
|
]
|
|
40
40
|
# TODO: Update the comment on initialize to describe every option
|
|
@@ -47,6 +47,8 @@ module OneLogin
|
|
|
47
47
|
# or :matches_request_id that will validate that the response matches the ID of the request,
|
|
48
48
|
# or skip the subject confirmation validation with the :skip_subject_confirmation option
|
|
49
49
|
# or skip the recipient validation of the subject confirmation element with :skip_recipient_check option
|
|
50
|
+
# or skip the audience validation with :skip_audience option
|
|
51
|
+
#
|
|
50
52
|
def initialize(response, options = {})
|
|
51
53
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
|
52
54
|
|
|
@@ -341,6 +343,28 @@ module OneLogin
|
|
|
341
343
|
return options[:allowed_clock_drift].to_f
|
|
342
344
|
end
|
|
343
345
|
|
|
346
|
+
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
347
|
+
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
348
|
+
#
|
|
349
|
+
def assertion_encrypted?
|
|
350
|
+
! REXML::XPath.first(
|
|
351
|
+
document,
|
|
352
|
+
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
353
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
354
|
+
).nil?
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def response_id
|
|
358
|
+
id(document)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def assertion_id
|
|
362
|
+
@assertion_id ||= begin
|
|
363
|
+
node = xpath_first_from_signed_assertion("")
|
|
364
|
+
node.nil? ? nil : node.attributes['ID']
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
344
368
|
private
|
|
345
369
|
|
|
346
370
|
# Validates the SAML Response (calls several validation methods)
|
|
@@ -435,7 +459,7 @@ module OneLogin
|
|
|
435
459
|
# @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
|
|
436
460
|
#
|
|
437
461
|
def validate_id
|
|
438
|
-
unless
|
|
462
|
+
unless response_id
|
|
439
463
|
return append_error("Missing ID attribute on SAML Response")
|
|
440
464
|
end
|
|
441
465
|
|
|
@@ -584,16 +608,18 @@ module OneLogin
|
|
|
584
608
|
end
|
|
585
609
|
|
|
586
610
|
# Validates the Audience, (If the Audience match the Service Provider EntityID)
|
|
611
|
+
# If the response was initialized with the :skip_audience option, this validation is skipped,
|
|
587
612
|
# If fails, the error is added to the errors array
|
|
588
613
|
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
|
|
589
614
|
# @raise [ValidationError] if soft == false and validation fails
|
|
590
615
|
#
|
|
591
616
|
def validate_audience
|
|
592
|
-
return true if
|
|
617
|
+
return true if options[:skip_audience]
|
|
618
|
+
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
|
593
619
|
|
|
594
|
-
unless audiences.include? settings.
|
|
620
|
+
unless audiences.include? settings.sp_entity_id
|
|
595
621
|
s = audiences.count > 1 ? 's' : '';
|
|
596
|
-
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.
|
|
622
|
+
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
|
|
597
623
|
return append_error(error_msg)
|
|
598
624
|
end
|
|
599
625
|
|
|
@@ -781,8 +807,8 @@ module OneLogin
|
|
|
781
807
|
return append_error("An empty NameID value found")
|
|
782
808
|
end
|
|
783
809
|
|
|
784
|
-
unless settings.
|
|
785
|
-
if name_id_spnamequalifier != settings.
|
|
810
|
+
unless settings.sp_entity_id.nil? || settings.sp_entity_id.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
|
|
811
|
+
if name_id_spnamequalifier != settings.sp_entity_id
|
|
786
812
|
return append_error("The SPNameQualifier value mistmatch the SP entityID value.")
|
|
787
813
|
end
|
|
788
814
|
end
|
|
@@ -821,28 +847,59 @@ module OneLogin
|
|
|
821
847
|
end
|
|
822
848
|
|
|
823
849
|
if sig_elements.size != 1
|
|
850
|
+
if sig_elements.size == 0
|
|
851
|
+
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
|
852
|
+
else
|
|
853
|
+
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
|
854
|
+
end
|
|
824
855
|
return append_error(error_msg)
|
|
825
856
|
end
|
|
826
857
|
|
|
858
|
+
old_errors = @errors.clone
|
|
859
|
+
|
|
827
860
|
idp_certs = settings.get_idp_cert_multi
|
|
828
861
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
829
862
|
opts = {}
|
|
830
863
|
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
|
|
831
|
-
|
|
864
|
+
idp_cert = settings.get_idp_cert
|
|
832
865
|
fingerprint = settings.get_fingerprint
|
|
866
|
+
opts[:cert] = idp_cert
|
|
833
867
|
|
|
834
|
-
|
|
868
|
+
if fingerprint && doc.validate_document(fingerprint, @soft, opts)
|
|
869
|
+
if settings.security[:check_idp_cert_expiration]
|
|
870
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
871
|
+
error_msg = "IdP x509 certificate expired"
|
|
872
|
+
return append_error(error_msg)
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
else
|
|
835
876
|
return append_error(error_msg)
|
|
836
877
|
end
|
|
837
878
|
else
|
|
838
879
|
valid = false
|
|
880
|
+
expired = false
|
|
839
881
|
idp_certs[:signing].each do |idp_cert|
|
|
840
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
|
882
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
|
841
883
|
if valid
|
|
884
|
+
if settings.security[:check_idp_cert_expiration]
|
|
885
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
886
|
+
expired = true
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
# At least one certificate is valid, restore the old accumulated errors
|
|
891
|
+
@errors = old_errors
|
|
842
892
|
break
|
|
843
893
|
end
|
|
894
|
+
|
|
895
|
+
end
|
|
896
|
+
if expired
|
|
897
|
+
error_msg = "IdP x509 certificate expired"
|
|
898
|
+
return append_error(error_msg)
|
|
844
899
|
end
|
|
845
900
|
unless valid
|
|
901
|
+
# Remove duplicated errors
|
|
902
|
+
@errors = @errors.uniq
|
|
846
903
|
return append_error(error_msg)
|
|
847
904
|
end
|
|
848
905
|
end
|
|
@@ -943,17 +1000,6 @@ module OneLogin
|
|
|
943
1000
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
|
944
1001
|
end
|
|
945
1002
|
|
|
946
|
-
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
947
|
-
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
948
|
-
#
|
|
949
|
-
def assertion_encrypted?
|
|
950
|
-
! REXML::XPath.first(
|
|
951
|
-
document,
|
|
952
|
-
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
953
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
954
|
-
).nil?
|
|
955
|
-
end
|
|
956
|
-
|
|
957
1003
|
# Decrypts an EncryptedAssertion element
|
|
958
1004
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
|
959
1005
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|
|
@@ -22,6 +22,8 @@ module OneLogin
|
|
|
22
22
|
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
|
|
23
23
|
@@mutex = Mutex.new
|
|
24
24
|
|
|
25
|
+
MAX_BYTE_SIZE = 250000
|
|
26
|
+
|
|
25
27
|
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
|
|
26
28
|
#
|
|
27
29
|
def self.schema
|
|
@@ -89,6 +91,10 @@ module OneLogin
|
|
|
89
91
|
def decode_raw_saml(saml)
|
|
90
92
|
return saml unless base64_encoded?(saml)
|
|
91
93
|
|
|
94
|
+
if saml.bytesize > MAX_BYTE_SIZE
|
|
95
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
|
|
96
|
+
end
|
|
97
|
+
|
|
92
98
|
decoded = decode(saml)
|
|
93
99
|
begin
|
|
94
100
|
inflate(decoded)
|
|
@@ -105,7 +111,7 @@ module OneLogin
|
|
|
105
111
|
def encode_raw_saml(saml, settings)
|
|
106
112
|
saml = deflate(saml) if settings.compress_request
|
|
107
113
|
|
|
108
|
-
CGI.escape(
|
|
114
|
+
CGI.escape(encode(saml))
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
# Base 64 decode method
|
|
@@ -121,7 +127,11 @@ module OneLogin
|
|
|
121
127
|
# @return [String] The encoded string
|
|
122
128
|
#
|
|
123
129
|
def encode(string)
|
|
124
|
-
Base64.
|
|
130
|
+
if Base64.respond_to?('strict_encode64')
|
|
131
|
+
Base64.strict_encode64(string)
|
|
132
|
+
else
|
|
133
|
+
Base64.encode64(string).gsub(/\n/, "")
|
|
134
|
+
end
|
|
125
135
|
end
|
|
126
136
|
|
|
127
137
|
# Check if a string is base64 encoded
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "xml_security"
|
|
2
2
|
require "onelogin/ruby-saml/attribute_service"
|
|
3
3
|
require "onelogin/ruby-saml/utils"
|
|
4
|
+
require "onelogin/ruby-saml/validation_error"
|
|
4
5
|
|
|
5
6
|
# Only supports SAML 2.0
|
|
6
7
|
module OneLogin
|
|
@@ -30,21 +31,24 @@ module OneLogin
|
|
|
30
31
|
|
|
31
32
|
# IdP Data
|
|
32
33
|
attr_accessor :idp_entity_id
|
|
33
|
-
|
|
34
|
-
attr_accessor :
|
|
34
|
+
|
|
35
|
+
attr_accessor :idp_sso_service_url
|
|
36
|
+
attr_accessor :idp_slo_service_url
|
|
37
|
+
attr_accessor :idp_slo_response_service_url
|
|
35
38
|
attr_accessor :idp_cert
|
|
36
39
|
attr_accessor :idp_cert_fingerprint
|
|
37
40
|
attr_accessor :idp_cert_fingerprint_algorithm
|
|
38
41
|
attr_accessor :idp_cert_multi
|
|
39
42
|
attr_accessor :idp_attribute_names
|
|
40
43
|
attr_accessor :idp_name_qualifier
|
|
44
|
+
attr_accessor :valid_until
|
|
41
45
|
# SP Data
|
|
42
|
-
attr_accessor :issuer
|
|
43
46
|
attr_accessor :assertion_consumer_service_url
|
|
44
47
|
attr_accessor :assertion_consumer_service_binding
|
|
45
48
|
attr_accessor :sp_name_qualifier
|
|
46
49
|
attr_accessor :name_identifier_format
|
|
47
50
|
attr_accessor :name_identifier_value
|
|
51
|
+
attr_accessor :name_identifier_value_requested
|
|
48
52
|
attr_accessor :sessionindex
|
|
49
53
|
attr_accessor :compress_request
|
|
50
54
|
attr_accessor :compress_response
|
|
@@ -66,6 +70,58 @@ module OneLogin
|
|
|
66
70
|
# Compability
|
|
67
71
|
attr_accessor :assertion_consumer_logout_service_url
|
|
68
72
|
attr_accessor :assertion_consumer_logout_service_binding
|
|
73
|
+
attr_accessor :issuer
|
|
74
|
+
attr_accessor :idp_sso_target_url
|
|
75
|
+
attr_accessor :idp_slo_target_url
|
|
76
|
+
|
|
77
|
+
# @return [String] IdP Single Sign On Service URL
|
|
78
|
+
#
|
|
79
|
+
def idp_sso_service_url
|
|
80
|
+
val = nil
|
|
81
|
+
if @idp_sso_service_url.nil?
|
|
82
|
+
if @idp_sso_target_url
|
|
83
|
+
val = @idp_sso_target_url
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
val = @idp_sso_service_url
|
|
87
|
+
end
|
|
88
|
+
val
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String] IdP Single Logout Service URL
|
|
92
|
+
#
|
|
93
|
+
def idp_slo_service_url
|
|
94
|
+
val = nil
|
|
95
|
+
if @idp_slo_service_url.nil?
|
|
96
|
+
if @idp_slo_target_url
|
|
97
|
+
val = @idp_slo_target_url
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
val = @idp_slo_service_url
|
|
101
|
+
end
|
|
102
|
+
val
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [String] SP Entity ID
|
|
106
|
+
#
|
|
107
|
+
def sp_entity_id
|
|
108
|
+
val = nil
|
|
109
|
+
if @sp_entity_id.nil?
|
|
110
|
+
if @issuer
|
|
111
|
+
val = @issuer
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
val = @sp_entity_id
|
|
115
|
+
end
|
|
116
|
+
val
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Setter for SP Entity ID.
|
|
120
|
+
# @param val [String].
|
|
121
|
+
#
|
|
122
|
+
def sp_entity_id=(val)
|
|
123
|
+
@sp_entity_id = val
|
|
124
|
+
end
|
|
69
125
|
|
|
70
126
|
# @return [String] Single Logout Service URL.
|
|
71
127
|
#
|
|
@@ -165,7 +221,15 @@ module OneLogin
|
|
|
165
221
|
return nil if certificate.nil? || certificate.empty?
|
|
166
222
|
|
|
167
223
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
|
168
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
|
224
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
|
225
|
+
|
|
226
|
+
if security[:check_sp_cert_expiration]
|
|
227
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
|
228
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
cert
|
|
169
233
|
end
|
|
170
234
|
|
|
171
235
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
|
@@ -195,6 +259,7 @@ module OneLogin
|
|
|
195
259
|
:compress_request => true,
|
|
196
260
|
:compress_response => true,
|
|
197
261
|
:soft => true,
|
|
262
|
+
:double_quote_xml_attribute_values => false,
|
|
198
263
|
:security => {
|
|
199
264
|
:authn_requests_signed => false,
|
|
200
265
|
:logout_requests_signed => false,
|
|
@@ -205,9 +270,10 @@ module OneLogin
|
|
|
205
270
|
:metadata_signed => false,
|
|
206
271
|
:embed_sign => false,
|
|
207
272
|
:digest_method => XMLSecurity::Document::SHA1,
|
|
208
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
|
209
|
-
|
|
210
|
-
|
|
273
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
|
274
|
+
:check_idp_cert_expiration => false,
|
|
275
|
+
:check_sp_cert_expiration => false
|
|
276
|
+
}.freeze
|
|
211
277
|
}.freeze
|
|
212
278
|
end
|
|
213
279
|
end
|