ruby-saml 1.11.0 → 1.13.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 +4 -4
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +44 -1
- data/README.md +333 -217
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +11 -7
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
- data/lib/onelogin/ruby-saml/logoutrequest.rb +12 -6
- data/lib/onelogin/ruby-saml/logoutresponse.rb +5 -1
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +51 -31
- data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +89 -49
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +16 -4
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
- data/lib/onelogin/ruby-saml/utils.rb +63 -2
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +39 -13
- data/ruby-saml.gemspec +8 -4
- metadata +24 -282
- data/.travis.yml +0 -46
- data/test/certificates/certificate.der +0 -0
- 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 -594
- 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 -86
- data/test/logoutrequest_test.rb +0 -260
- data/test/logoutresponse_test.rb +0 -427
- 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 -59
- data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -340
- data/test/response_test.rb +0 -1629
- 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 -338
- data/test/slo_logoutrequest_test.rb +0 -467
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -333
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
|
@@ -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
|
|
@@ -27,13 +32,14 @@ module OneLogin
|
|
|
27
32
|
#
|
|
28
33
|
def create(settings, params={})
|
|
29
34
|
params = create_params(settings, params)
|
|
30
|
-
params_prefix = (settings.
|
|
35
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
|
31
36
|
saml_request = CGI.escape(params.delete("SAMLRequest"))
|
|
32
37
|
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
|
|
33
38
|
params.each_pair do |key, value|
|
|
34
39
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
35
40
|
end
|
|
36
|
-
|
|
41
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
|
|
42
|
+
@logout_url = settings.idp_slo_service_url + request_params
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
# Creates the Get parameters for the logout request.
|
|
@@ -64,8 +70,8 @@ module OneLogin
|
|
|
64
70
|
base64_request = encode(request)
|
|
65
71
|
request_params = {"SAMLRequest" => base64_request}
|
|
66
72
|
|
|
67
|
-
if settings.
|
|
68
|
-
params['SigAlg']
|
|
73
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && settings.private_key
|
|
74
|
+
params['SigAlg'] = settings.security[:signature_method]
|
|
69
75
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
70
76
|
:type => 'SAMLRequest',
|
|
71
77
|
:data => base64_request,
|
|
@@ -103,7 +109,7 @@ module OneLogin
|
|
|
103
109
|
root.attributes['ID'] = uuid
|
|
104
110
|
root.attributes['IssueInstant'] = time
|
|
105
111
|
root.attributes['Version'] = "2.0"
|
|
106
|
-
root.attributes['Destination'] = settings.
|
|
112
|
+
root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
|
|
107
113
|
|
|
108
114
|
if settings.sp_entity_id
|
|
109
115
|
issuer = root.add_element "saml:Issuer"
|
|
@@ -132,7 +138,7 @@ module OneLogin
|
|
|
132
138
|
|
|
133
139
|
def sign_document(document, settings)
|
|
134
140
|
# embed signature
|
|
135
|
-
if settings.
|
|
141
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && settings.private_key && settings.certificate
|
|
136
142
|
private_key = settings.get_sp_key
|
|
137
143
|
cert = settings.get_sp_cert
|
|
138
144
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
@@ -43,10 +43,14 @@ module OneLogin
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
@options = options
|
|
46
|
-
@response = decode_raw_saml(response)
|
|
46
|
+
@response = decode_raw_saml(response, settings)
|
|
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
|
|
@@ -15,25 +15,56 @@ 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
|
|
24
|
+
add_xml_declaration(meta_doc)
|
|
25
|
+
root = add_root_element(meta_doc, settings, valid_until, cache_duration)
|
|
26
|
+
sp_sso = add_sp_sso_element(root, settings)
|
|
27
|
+
add_sp_certificates(sp_sso, settings)
|
|
28
|
+
add_sp_service_elements(sp_sso, settings)
|
|
29
|
+
add_extras(root, settings)
|
|
30
|
+
embed_signature(meta_doc, settings)
|
|
31
|
+
output_xml(meta_doc, pretty_print)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
def add_xml_declaration(meta_doc)
|
|
37
|
+
meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_root_element(meta_doc, settings, valid_until, cache_duration)
|
|
22
41
|
namespaces = {
|
|
23
42
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
24
43
|
}
|
|
44
|
+
|
|
25
45
|
if settings.attribute_consuming_service.configured?
|
|
26
46
|
namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
27
47
|
end
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
|
|
49
|
+
root = meta_doc.add_element("md:EntityDescriptor", namespaces)
|
|
50
|
+
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
|
51
|
+
root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id
|
|
52
|
+
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') if valid_until
|
|
53
|
+
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration
|
|
54
|
+
root
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_sp_sso_element(root, settings)
|
|
58
|
+
root.add_element "md:SPSSODescriptor", {
|
|
30
59
|
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
31
60
|
"AuthnRequestsSigned" => settings.security[:authn_requests_signed],
|
|
32
61
|
"WantAssertionsSigned" => settings.security[:want_assertions_signed],
|
|
33
62
|
}
|
|
63
|
+
end
|
|
34
64
|
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
# Add KeyDescriptor if messages will be signed / encrypted
|
|
66
|
+
# with SP certificate, and new SP certificate if any
|
|
67
|
+
def add_sp_certificates(sp_sso, settings)
|
|
37
68
|
cert = settings.get_sp_cert
|
|
38
69
|
cert_new = settings.get_sp_cert_new
|
|
39
70
|
|
|
@@ -56,10 +87,10 @@ module OneLogin
|
|
|
56
87
|
end
|
|
57
88
|
end
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
sp_sso
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_sp_service_elements(sp_sso, settings)
|
|
63
94
|
if settings.single_logout_service_url
|
|
64
95
|
sp_sso.add_element "md:SingleLogoutService", {
|
|
65
96
|
"Binding" => settings.single_logout_service_binding,
|
|
@@ -67,10 +98,12 @@ module OneLogin
|
|
|
67
98
|
"ResponseLocation" => settings.single_logout_service_url
|
|
68
99
|
}
|
|
69
100
|
end
|
|
101
|
+
|
|
70
102
|
if settings.name_identifier_format
|
|
71
103
|
nameid = sp_sso.add_element "md:NameIDFormat"
|
|
72
104
|
nameid.text = settings.name_identifier_format
|
|
73
105
|
end
|
|
106
|
+
|
|
74
107
|
if settings.assertion_consumer_service_url
|
|
75
108
|
sp_sso.add_element "md:AssertionConsumerService", {
|
|
76
109
|
"Binding" => settings.assertion_consumer_service_binding,
|
|
@@ -109,15 +142,27 @@ module OneLogin
|
|
|
109
142
|
# <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
|
110
143
|
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
|
111
144
|
|
|
112
|
-
|
|
145
|
+
sp_sso
|
|
146
|
+
end
|
|
113
147
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
148
|
+
# can be overridden in subclass
|
|
149
|
+
def add_extras(root, _settings)
|
|
150
|
+
root
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def embed_signature(meta_doc, settings)
|
|
154
|
+
return unless settings.security[:metadata_signed]
|
|
155
|
+
|
|
156
|
+
private_key = settings.get_sp_key
|
|
157
|
+
cert = settings.get_sp_cert
|
|
158
|
+
return unless private_key && cert
|
|
159
|
+
|
|
160
|
+
meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def output_xml(meta_doc, pretty_print)
|
|
164
|
+
ret = ''
|
|
119
165
|
|
|
120
|
-
ret = ""
|
|
121
166
|
# pretty print the XML so IdP administrators can easily see what the SP supports
|
|
122
167
|
if pretty_print
|
|
123
168
|
meta_doc.write(ret, 1)
|
|
@@ -125,7 +170,7 @@ module OneLogin
|
|
|
125
170
|
ret = meta_doc.to_s
|
|
126
171
|
end
|
|
127
172
|
|
|
128
|
-
|
|
173
|
+
ret
|
|
129
174
|
end
|
|
130
175
|
end
|
|
131
176
|
end
|
|
@@ -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
|
|
|
@@ -61,7 +63,7 @@ module OneLogin
|
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
64
|
-
@response = decode_raw_saml(response)
|
|
66
|
+
@response = decode_raw_saml(response, settings)
|
|
65
67
|
@document = XMLSecurity::SignedDocument.new(@response, @errors)
|
|
66
68
|
|
|
67
69
|
if assertion_encrypted?
|
|
@@ -225,11 +227,10 @@ module OneLogin
|
|
|
225
227
|
statuses = nodes.collect do |inner_node|
|
|
226
228
|
inner_node.attributes["Value"]
|
|
227
229
|
end
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
code = "#{code} | #{extra_code}"
|
|
231
|
-
end
|
|
230
|
+
|
|
231
|
+
code = [code, statuses].flatten.join(" | ")
|
|
232
232
|
end
|
|
233
|
+
|
|
233
234
|
code
|
|
234
235
|
end
|
|
235
236
|
end
|
|
@@ -336,9 +337,31 @@ module OneLogin
|
|
|
336
337
|
end
|
|
337
338
|
|
|
338
339
|
# returns the allowed clock drift on timing validation
|
|
339
|
-
# @return [
|
|
340
|
+
# @return [Float]
|
|
340
341
|
def allowed_clock_drift
|
|
341
|
-
|
|
342
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
346
|
+
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
347
|
+
#
|
|
348
|
+
def assertion_encrypted?
|
|
349
|
+
! REXML::XPath.first(
|
|
350
|
+
document,
|
|
351
|
+
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
352
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
353
|
+
).nil?
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def response_id
|
|
357
|
+
id(document)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def assertion_id
|
|
361
|
+
@assertion_id ||= begin
|
|
362
|
+
node = xpath_first_from_signed_assertion("")
|
|
363
|
+
node.nil? ? nil : node.attributes['ID']
|
|
364
|
+
end
|
|
342
365
|
end
|
|
343
366
|
|
|
344
367
|
private
|
|
@@ -353,7 +376,6 @@ module OneLogin
|
|
|
353
376
|
return false unless validate_response_state
|
|
354
377
|
|
|
355
378
|
validations = [
|
|
356
|
-
:validate_response_state,
|
|
357
379
|
:validate_version,
|
|
358
380
|
:validate_id,
|
|
359
381
|
:validate_success_status,
|
|
@@ -435,7 +457,7 @@ module OneLogin
|
|
|
435
457
|
# @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
|
|
436
458
|
#
|
|
437
459
|
def validate_id
|
|
438
|
-
unless
|
|
460
|
+
unless response_id
|
|
439
461
|
return append_error("Missing ID attribute on SAML Response")
|
|
440
462
|
end
|
|
441
463
|
|
|
@@ -584,11 +606,13 @@ module OneLogin
|
|
|
584
606
|
end
|
|
585
607
|
|
|
586
608
|
# Validates the Audience, (If the Audience match the Service Provider EntityID)
|
|
609
|
+
# If the response was initialized with the :skip_audience option, this validation is skipped,
|
|
587
610
|
# If fails, the error is added to the errors array
|
|
588
611
|
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
|
|
589
612
|
# @raise [ValidationError] if soft == false and validation fails
|
|
590
613
|
#
|
|
591
614
|
def validate_audience
|
|
615
|
+
return true if options[:skip_audience]
|
|
592
616
|
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
|
593
617
|
|
|
594
618
|
unless audiences.include? settings.sp_entity_id
|
|
@@ -668,13 +692,13 @@ module OneLogin
|
|
|
668
692
|
|
|
669
693
|
now = Time.now.utc
|
|
670
694
|
|
|
671
|
-
if not_before &&
|
|
672
|
-
error_msg = "Current time is earlier than NotBefore condition (#{
|
|
695
|
+
if not_before && now < (not_before - allowed_clock_drift)
|
|
696
|
+
error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
|
|
673
697
|
return append_error(error_msg)
|
|
674
698
|
end
|
|
675
699
|
|
|
676
|
-
if not_on_or_after && now >= (
|
|
677
|
-
error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{
|
|
700
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
|
701
|
+
error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
|
|
678
702
|
return append_error(error_msg)
|
|
679
703
|
end
|
|
680
704
|
|
|
@@ -716,7 +740,7 @@ module OneLogin
|
|
|
716
740
|
return true if session_expires_at.nil?
|
|
717
741
|
|
|
718
742
|
now = Time.now.utc
|
|
719
|
-
unless (session_expires_at + allowed_clock_drift)
|
|
743
|
+
unless now < (session_expires_at + allowed_clock_drift)
|
|
720
744
|
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
|
|
721
745
|
return append_error(error_msg)
|
|
722
746
|
end
|
|
@@ -754,8 +778,8 @@ module OneLogin
|
|
|
754
778
|
|
|
755
779
|
attrs = confirmation_data_node.attributes
|
|
756
780
|
next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
|
|
757
|
-
(attrs.include? "
|
|
758
|
-
(attrs.include? "
|
|
781
|
+
(attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) ||
|
|
782
|
+
(attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) ||
|
|
759
783
|
(attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
|
|
760
784
|
|
|
761
785
|
valid_subject_confirmation = true
|
|
@@ -802,7 +826,7 @@ module OneLogin
|
|
|
802
826
|
# otherwise, review if the decrypted assertion contains a signature
|
|
803
827
|
sig_elements = REXML::XPath.match(
|
|
804
828
|
document,
|
|
805
|
-
"/p:Response[@ID=$id]/ds:Signature
|
|
829
|
+
"/p:Response[@ID=$id]/ds:Signature",
|
|
806
830
|
{ "p" => PROTOCOL, "ds" => DSIG },
|
|
807
831
|
{ 'id' => document.signed_element_id }
|
|
808
832
|
)
|
|
@@ -821,7 +845,7 @@ module OneLogin
|
|
|
821
845
|
end
|
|
822
846
|
|
|
823
847
|
if sig_elements.size != 1
|
|
824
|
-
if
|
|
848
|
+
if sig_elements.size == 0
|
|
825
849
|
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
|
826
850
|
else
|
|
827
851
|
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
|
@@ -829,6 +853,7 @@ module OneLogin
|
|
|
829
853
|
return append_error(error_msg)
|
|
830
854
|
end
|
|
831
855
|
|
|
856
|
+
old_errors = @errors.clone
|
|
832
857
|
|
|
833
858
|
idp_certs = settings.get_idp_cert_multi
|
|
834
859
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
@@ -852,21 +877,27 @@ module OneLogin
|
|
|
852
877
|
valid = false
|
|
853
878
|
expired = false
|
|
854
879
|
idp_certs[:signing].each do |idp_cert|
|
|
855
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
|
880
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
|
856
881
|
if valid
|
|
857
882
|
if settings.security[:check_idp_cert_expiration]
|
|
858
883
|
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
859
884
|
expired = true
|
|
860
885
|
end
|
|
861
886
|
end
|
|
887
|
+
|
|
888
|
+
# At least one certificate is valid, restore the old accumulated errors
|
|
889
|
+
@errors = old_errors
|
|
862
890
|
break
|
|
863
891
|
end
|
|
892
|
+
|
|
864
893
|
end
|
|
865
894
|
if expired
|
|
866
895
|
error_msg = "IdP x509 certificate expired"
|
|
867
896
|
return append_error(error_msg)
|
|
868
897
|
end
|
|
869
898
|
unless valid
|
|
899
|
+
# Remove duplicated errors
|
|
900
|
+
@errors = @errors.uniq
|
|
870
901
|
return append_error(error_msg)
|
|
871
902
|
end
|
|
872
903
|
end
|
|
@@ -967,17 +998,6 @@ module OneLogin
|
|
|
967
998
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
|
968
999
|
end
|
|
969
1000
|
|
|
970
|
-
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
971
|
-
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
972
|
-
#
|
|
973
|
-
def assertion_encrypted?
|
|
974
|
-
! REXML::XPath.first(
|
|
975
|
-
document,
|
|
976
|
-
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
977
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
978
|
-
).nil?
|
|
979
|
-
end
|
|
980
|
-
|
|
981
1001
|
# Decrypts an EncryptedAssertion element
|
|
982
1002
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
|
983
1003
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|
|
@@ -16,8 +16,8 @@ module OneLogin
|
|
|
16
16
|
class SamlMessage
|
|
17
17
|
include REXML
|
|
18
18
|
|
|
19
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
20
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
19
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
|
|
20
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
|
|
21
21
|
|
|
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
|
|
@@ -86,9 +86,14 @@ module OneLogin
|
|
|
86
86
|
# @param saml [String] The deflated and encoded SAML Message
|
|
87
87
|
# @return [String] The plain SAML Message
|
|
88
88
|
#
|
|
89
|
-
def decode_raw_saml(saml)
|
|
89
|
+
def decode_raw_saml(saml, settings = nil)
|
|
90
90
|
return saml unless base64_encoded?(saml)
|
|
91
91
|
|
|
92
|
+
settings = OneLogin::RubySaml::Settings.new if settings.nil?
|
|
93
|
+
if saml.bytesize > settings.message_max_bytesize
|
|
94
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
|
|
95
|
+
end
|
|
96
|
+
|
|
92
97
|
decoded = decode(saml)
|
|
93
98
|
begin
|
|
94
99
|
inflate(decoded)
|