ruby-saml 1.9.0 → 1.14.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/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +64 -1
- data/README.md +394 -211
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +26 -10
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +26 -11
- data/lib/onelogin/ruby-saml/logoutresponse.rb +27 -11
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +86 -37
- data/lib/onelogin/ruby-saml/saml_message.rb +14 -5
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +117 -41
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +33 -31
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +43 -20
- data/lib/onelogin/ruby-saml/utils.rb +101 -9
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +39 -13
- data/ruby-saml.gemspec +21 -8
- metadata +43 -284
- data/.travis.yml +0 -32
- 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
|
@@ -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,27 +31,32 @@ module OneLogin
|
|
|
30
31
|
|
|
31
32
|
# IdP Data
|
|
32
33
|
attr_accessor :idp_entity_id
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
attr_writer :idp_sso_service_url
|
|
35
|
+
attr_writer :idp_slo_service_url
|
|
36
|
+
attr_accessor :idp_slo_response_service_url
|
|
35
37
|
attr_accessor :idp_cert
|
|
36
38
|
attr_accessor :idp_cert_fingerprint
|
|
37
39
|
attr_accessor :idp_cert_fingerprint_algorithm
|
|
38
40
|
attr_accessor :idp_cert_multi
|
|
39
41
|
attr_accessor :idp_attribute_names
|
|
40
42
|
attr_accessor :idp_name_qualifier
|
|
43
|
+
attr_accessor :valid_until
|
|
41
44
|
# SP Data
|
|
42
|
-
|
|
45
|
+
attr_writer :sp_entity_id
|
|
43
46
|
attr_accessor :assertion_consumer_service_url
|
|
44
|
-
|
|
47
|
+
attr_reader :assertion_consumer_service_binding
|
|
48
|
+
attr_writer :single_logout_service_url
|
|
45
49
|
attr_accessor :sp_name_qualifier
|
|
46
50
|
attr_accessor :name_identifier_format
|
|
47
51
|
attr_accessor :name_identifier_value
|
|
52
|
+
attr_accessor :name_identifier_value_requested
|
|
48
53
|
attr_accessor :sessionindex
|
|
49
54
|
attr_accessor :compress_request
|
|
50
55
|
attr_accessor :compress_response
|
|
51
56
|
attr_accessor :double_quote_xml_attribute_values
|
|
57
|
+
attr_accessor :message_max_bytesize
|
|
52
58
|
attr_accessor :passive
|
|
53
|
-
|
|
59
|
+
attr_reader :protocol_binding
|
|
54
60
|
attr_accessor :attributes_index
|
|
55
61
|
attr_accessor :force_authn
|
|
56
62
|
attr_accessor :certificate
|
|
@@ -63,52 +69,99 @@ module OneLogin
|
|
|
63
69
|
# Work-flow
|
|
64
70
|
attr_accessor :security
|
|
65
71
|
attr_accessor :soft
|
|
66
|
-
#
|
|
72
|
+
# Deprecated
|
|
67
73
|
attr_accessor :assertion_consumer_logout_service_url
|
|
68
|
-
|
|
74
|
+
attr_reader :assertion_consumer_logout_service_binding
|
|
75
|
+
attr_accessor :issuer
|
|
76
|
+
attr_accessor :idp_sso_target_url
|
|
77
|
+
attr_accessor :idp_slo_target_url
|
|
69
78
|
|
|
70
|
-
# @return [String] Single
|
|
79
|
+
# @return [String] IdP Single Sign On Service URL
|
|
71
80
|
#
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
def idp_sso_service_url
|
|
82
|
+
@idp_sso_service_url || @idp_sso_target_url
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [String] IdP Single Logout Service URL
|
|
86
|
+
#
|
|
87
|
+
def idp_slo_service_url
|
|
88
|
+
@idp_slo_service_url || @idp_slo_target_url
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String] IdP Single Sign On Service Binding
|
|
92
|
+
#
|
|
93
|
+
def idp_sso_service_binding
|
|
94
|
+
@idp_sso_service_binding || idp_binding_from_embed_sign
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Setter for IdP Single Sign On Service Binding
|
|
98
|
+
# @param value [String, Symbol].
|
|
99
|
+
#
|
|
100
|
+
def idp_sso_service_binding=(value)
|
|
101
|
+
@idp_sso_service_binding = get_binding(value)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [String] IdP Single Logout Service Binding
|
|
105
|
+
#
|
|
106
|
+
def idp_slo_service_binding
|
|
107
|
+
@idp_slo_service_binding || idp_binding_from_embed_sign
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Setter for IdP Single Logout Service Binding
|
|
111
|
+
# @param value [String, Symbol].
|
|
112
|
+
#
|
|
113
|
+
def idp_slo_service_binding=(value)
|
|
114
|
+
@idp_slo_service_binding = get_binding(value)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @return [String] SP Entity ID
|
|
118
|
+
#
|
|
119
|
+
def sp_entity_id
|
|
120
|
+
@sp_entity_id || @issuer
|
|
82
121
|
end
|
|
83
122
|
|
|
84
|
-
# Setter for
|
|
85
|
-
# @param
|
|
123
|
+
# Setter for SP Protocol Binding
|
|
124
|
+
# @param value [String, Symbol].
|
|
86
125
|
#
|
|
87
|
-
def
|
|
88
|
-
@
|
|
126
|
+
def protocol_binding=(value)
|
|
127
|
+
@protocol_binding = get_binding(value)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Setter for SP Assertion Consumer Service Binding
|
|
131
|
+
# @param value [String, Symbol].
|
|
132
|
+
#
|
|
133
|
+
def assertion_consumer_service_binding=(value)
|
|
134
|
+
@assertion_consumer_service_binding = get_binding(value)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @return [String] Single Logout Service URL.
|
|
138
|
+
#
|
|
139
|
+
def single_logout_service_url
|
|
140
|
+
@single_logout_service_url || @assertion_consumer_logout_service_url
|
|
89
141
|
end
|
|
90
142
|
|
|
91
143
|
# @return [String] Single Logout Service Binding.
|
|
92
144
|
#
|
|
93
145
|
def single_logout_service_binding
|
|
94
|
-
|
|
95
|
-
if @single_logout_service_binding.nil?
|
|
96
|
-
if @assertion_consumer_logout_service_binding
|
|
97
|
-
val = @assertion_consumer_logout_service_binding
|
|
98
|
-
end
|
|
99
|
-
else
|
|
100
|
-
val = @single_logout_service_binding
|
|
101
|
-
end
|
|
102
|
-
val
|
|
146
|
+
@single_logout_service_binding || @assertion_consumer_logout_service_binding
|
|
103
147
|
end
|
|
104
148
|
|
|
105
149
|
# Setter for Single Logout Service Binding.
|
|
106
150
|
#
|
|
107
151
|
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
|
108
|
-
# @param
|
|
152
|
+
# @param value [String, Symbol]
|
|
153
|
+
#
|
|
154
|
+
def single_logout_service_binding=(value)
|
|
155
|
+
@single_logout_service_binding = get_binding(value)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @deprecated Setter for legacy Single Logout Service Binding parameter.
|
|
159
|
+
#
|
|
160
|
+
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
|
161
|
+
# @param value [String, Symbol]
|
|
109
162
|
#
|
|
110
|
-
def
|
|
111
|
-
@
|
|
163
|
+
def assertion_consumer_logout_service_binding=(value)
|
|
164
|
+
@assertion_consumer_logout_service_binding = get_binding(value)
|
|
112
165
|
end
|
|
113
166
|
|
|
114
167
|
# Calculates the fingerprint of the IdP x509 certificate.
|
|
@@ -165,7 +218,15 @@ module OneLogin
|
|
|
165
218
|
return nil if certificate.nil? || certificate.empty?
|
|
166
219
|
|
|
167
220
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
|
168
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
|
221
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
|
222
|
+
|
|
223
|
+
if security[:check_sp_cert_expiration]
|
|
224
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
|
225
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
cert
|
|
169
230
|
end
|
|
170
231
|
|
|
171
232
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
|
@@ -188,13 +249,25 @@ module OneLogin
|
|
|
188
249
|
|
|
189
250
|
private
|
|
190
251
|
|
|
252
|
+
def idp_binding_from_embed_sign
|
|
253
|
+
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def get_binding(value)
|
|
257
|
+
return unless value
|
|
258
|
+
|
|
259
|
+
Utils::BINDINGS[value.to_sym] || value
|
|
260
|
+
end
|
|
261
|
+
|
|
191
262
|
DEFAULTS = {
|
|
192
|
-
:assertion_consumer_service_binding =>
|
|
193
|
-
:single_logout_service_binding =>
|
|
263
|
+
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
|
|
264
|
+
:single_logout_service_binding => Utils::BINDINGS[:redirect],
|
|
194
265
|
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
|
195
266
|
:compress_request => true,
|
|
196
267
|
:compress_response => true,
|
|
268
|
+
:message_max_bytesize => 250000,
|
|
197
269
|
:soft => true,
|
|
270
|
+
:double_quote_xml_attribute_values => false,
|
|
198
271
|
:security => {
|
|
199
272
|
:authn_requests_signed => false,
|
|
200
273
|
:logout_requests_signed => false,
|
|
@@ -203,11 +276,14 @@ module OneLogin
|
|
|
203
276
|
:want_assertions_encrypted => false,
|
|
204
277
|
:want_name_id => false,
|
|
205
278
|
:metadata_signed => false,
|
|
206
|
-
:embed_sign => false,
|
|
279
|
+
:embed_sign => false, # Deprecated
|
|
207
280
|
:digest_method => XMLSecurity::Document::SHA1,
|
|
208
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
|
209
|
-
|
|
210
|
-
|
|
281
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
|
282
|
+
:check_idp_cert_expiration => false,
|
|
283
|
+
:check_sp_cert_expiration => false,
|
|
284
|
+
:strict_audience_validation => false,
|
|
285
|
+
:lowercase_url_encoding => false
|
|
286
|
+
}.freeze
|
|
211
287
|
}.freeze
|
|
212
288
|
end
|
|
213
289
|
end
|
|
@@ -43,10 +43,14 @@ module OneLogin
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
@request = decode_raw_saml(request)
|
|
46
|
+
@request = decode_raw_saml(request, settings)
|
|
47
47
|
@document = REXML::Document.new(@request)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def request_id
|
|
51
|
+
id(document)
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
# Validates the Logout Request with the default values (soft = true)
|
|
51
55
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
|
|
52
56
|
# @return [Boolean] TRUE if the Logout Request is valid
|
|
@@ -126,6 +130,12 @@ module OneLogin
|
|
|
126
130
|
|
|
127
131
|
private
|
|
128
132
|
|
|
133
|
+
# returns the allowed clock drift on timing validation
|
|
134
|
+
# @return [Float]
|
|
135
|
+
def allowed_clock_drift
|
|
136
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
|
137
|
+
end
|
|
138
|
+
|
|
129
139
|
# Hard aux function to validate the Logout Request
|
|
130
140
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
|
131
141
|
# @return [Boolean] TRUE if the Logout Request is valid
|
|
@@ -176,15 +186,17 @@ module OneLogin
|
|
|
176
186
|
true
|
|
177
187
|
end
|
|
178
188
|
|
|
179
|
-
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
|
189
|
+
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
|
190
|
+
# option, the timing validations are relaxed by the allowed_clock_drift value)
|
|
180
191
|
# If fails, the error is added to the errors array
|
|
181
192
|
# @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
|
|
182
193
|
# @raise [ValidationError] if soft == false and validation fails
|
|
183
194
|
#
|
|
184
195
|
def validate_not_on_or_after
|
|
185
196
|
now = Time.now.utc
|
|
186
|
-
|
|
187
|
-
|
|
197
|
+
|
|
198
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
|
199
|
+
return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})")
|
|
188
200
|
end
|
|
189
201
|
|
|
190
202
|
true
|
|
@@ -236,32 +248,8 @@ module OneLogin
|
|
|
236
248
|
return true unless options.has_key? :get_params
|
|
237
249
|
return true unless options[:get_params].has_key? 'Signature'
|
|
238
250
|
|
|
239
|
-
|
|
240
|
-
# of URI-encoded values _as sent by the IDP_:
|
|
241
|
-
#
|
|
242
|
-
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
|
|
243
|
-
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
|
|
244
|
-
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
|
|
245
|
-
# > processed by software because the resulting encoding may not match the signer's encoding.
|
|
246
|
-
#
|
|
247
|
-
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
|
|
248
|
-
#
|
|
249
|
-
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
|
|
250
|
-
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
|
|
251
|
-
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
|
|
252
|
-
options[:raw_get_params] ||= {}
|
|
253
|
-
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
|
|
254
|
-
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
|
|
255
|
-
end
|
|
256
|
-
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
|
|
257
|
-
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
|
|
258
|
-
end
|
|
259
|
-
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
|
|
260
|
-
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
|
|
261
|
-
end
|
|
251
|
+
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
|
|
262
252
|
|
|
263
|
-
# If we only received the raw version of SigAlg,
|
|
264
|
-
# then parse it back into the decoded params hash for convenience.
|
|
265
253
|
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
|
|
266
254
|
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
|
|
267
255
|
end
|
|
@@ -280,13 +268,19 @@ module OneLogin
|
|
|
280
268
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
|
281
269
|
)
|
|
282
270
|
|
|
271
|
+
expired = false
|
|
283
272
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
284
273
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
|
285
|
-
:cert =>
|
|
274
|
+
:cert => idp_cert,
|
|
286
275
|
:sig_alg => options[:get_params]['SigAlg'],
|
|
287
276
|
:signature => options[:get_params]['Signature'],
|
|
288
277
|
:query_string => query_string
|
|
289
278
|
)
|
|
279
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
|
280
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
281
|
+
expired = true
|
|
282
|
+
end
|
|
283
|
+
end
|
|
290
284
|
else
|
|
291
285
|
valid = false
|
|
292
286
|
idp_certs[:signing].each do |signing_idp_cert|
|
|
@@ -297,18 +291,26 @@ module OneLogin
|
|
|
297
291
|
:query_string => query_string
|
|
298
292
|
)
|
|
299
293
|
if valid
|
|
294
|
+
if settings.security[:check_idp_cert_expiration]
|
|
295
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
|
296
|
+
expired = true
|
|
297
|
+
end
|
|
298
|
+
end
|
|
300
299
|
break
|
|
301
300
|
end
|
|
302
301
|
end
|
|
303
302
|
end
|
|
304
303
|
|
|
304
|
+
if expired
|
|
305
|
+
error_msg = "IdP x509 certificate expired"
|
|
306
|
+
return append_error(error_msg)
|
|
307
|
+
end
|
|
305
308
|
unless valid
|
|
306
309
|
return append_error("Invalid Signature on Logout Request")
|
|
307
310
|
end
|
|
308
311
|
|
|
309
312
|
true
|
|
310
313
|
end
|
|
311
|
-
|
|
312
314
|
end
|
|
313
315
|
end
|
|
314
316
|
end
|
|
@@ -2,6 +2,7 @@ require "onelogin/ruby-saml/logging"
|
|
|
2
2
|
|
|
3
3
|
require "onelogin/ruby-saml/saml_message"
|
|
4
4
|
require "onelogin/ruby-saml/utils"
|
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
|
5
6
|
|
|
6
7
|
# Only supports SAML 2.0
|
|
7
8
|
module OneLogin
|
|
@@ -12,7 +13,7 @@ module OneLogin
|
|
|
12
13
|
class SloLogoutresponse < SamlMessage
|
|
13
14
|
|
|
14
15
|
# Logout Response ID
|
|
15
|
-
|
|
16
|
+
attr_accessor :uuid
|
|
16
17
|
|
|
17
18
|
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
|
18
19
|
# Asigns an ID, a random uuid.
|
|
@@ -21,23 +22,30 @@ module OneLogin
|
|
|
21
22
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
def response_id
|
|
26
|
+
@uuid
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
# Creates the Logout Response string.
|
|
25
30
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
26
31
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
27
32
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
28
33
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
34
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
29
35
|
# @return [String] Logout Request string that includes the SAMLRequest
|
|
30
36
|
#
|
|
31
|
-
def create(settings, request_id = nil, logout_message = nil, params = {})
|
|
32
|
-
params = create_params(settings, request_id, logout_message, params)
|
|
33
|
-
params_prefix = (settings.
|
|
37
|
+
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
|
38
|
+
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
|
39
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
|
34
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
|
35
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
|
36
43
|
params.each_pair do |key, value|
|
|
37
44
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
38
45
|
end
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
|
48
|
+
@logout_url = url + response_params
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
# Creates the Get parameters for the logout response.
|
|
@@ -45,9 +53,10 @@ module OneLogin
|
|
|
45
53
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
46
54
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
47
55
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
56
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
48
57
|
# @return [Hash] Parameters
|
|
49
58
|
#
|
|
50
|
-
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
|
59
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
|
51
60
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
|
52
61
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
|
53
62
|
# conflicts so this line will solve them.
|
|
@@ -58,7 +67,7 @@ module OneLogin
|
|
|
58
67
|
params.delete('RelayState')
|
|
59
68
|
end
|
|
60
69
|
|
|
61
|
-
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
|
70
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
|
62
71
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
63
72
|
|
|
64
73
|
response = ""
|
|
@@ -70,7 +79,7 @@ module OneLogin
|
|
|
70
79
|
base64_response = encode(response)
|
|
71
80
|
response_params = {"SAMLResponse" => base64_response}
|
|
72
81
|
|
|
73
|
-
if settings.
|
|
82
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
|
|
74
83
|
params['SigAlg'] = settings.security[:signature_method]
|
|
75
84
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
76
85
|
:type => 'SAMLResponse',
|
|
@@ -94,46 +103,60 @@ module OneLogin
|
|
|
94
103
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
95
104
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
96
105
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
106
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
97
107
|
# @return [String] The SAMLResponse String.
|
|
98
108
|
#
|
|
99
|
-
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
|
109
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
|
|
110
|
+
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
|
|
111
|
+
sign_document(document, settings)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
|
100
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
101
116
|
|
|
102
117
|
response_doc = XMLSecurity::Document.new
|
|
103
118
|
response_doc.uuid = uuid
|
|
104
119
|
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
|
121
|
+
|
|
122
|
+
|
|
105
123
|
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
|
106
124
|
root.attributes['ID'] = uuid
|
|
107
125
|
root.attributes['IssueInstant'] = time
|
|
108
126
|
root.attributes['Version'] = '2.0'
|
|
109
127
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
|
110
|
-
root.attributes['Destination'] =
|
|
128
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
|
111
129
|
|
|
112
|
-
if settings.
|
|
130
|
+
if settings.sp_entity_id != nil
|
|
113
131
|
issuer = root.add_element "saml:Issuer"
|
|
114
|
-
issuer.text = settings.
|
|
132
|
+
issuer.text = settings.sp_entity_id
|
|
115
133
|
end
|
|
116
134
|
|
|
117
|
-
# add
|
|
135
|
+
# add status
|
|
118
136
|
status = root.add_element 'samlp:Status'
|
|
119
137
|
|
|
120
|
-
#
|
|
121
|
-
status_code
|
|
122
|
-
|
|
138
|
+
# status code
|
|
139
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
|
140
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
|
141
|
+
status_code_elem.attributes['Value'] = status_code
|
|
123
142
|
|
|
124
|
-
#
|
|
143
|
+
# status message
|
|
125
144
|
logout_message ||= 'Successfully Signed Out'
|
|
126
145
|
status_message = status.add_element 'samlp:StatusMessage'
|
|
127
146
|
status_message.text = logout_message
|
|
128
147
|
|
|
148
|
+
response_doc
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def sign_document(document, settings)
|
|
129
152
|
# embed signature
|
|
130
|
-
if settings.
|
|
153
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
|
|
131
154
|
private_key = settings.get_sp_key
|
|
132
155
|
cert = settings.get_sp_cert
|
|
133
|
-
|
|
156
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
134
157
|
end
|
|
135
158
|
|
|
136
|
-
|
|
159
|
+
document
|
|
137
160
|
end
|
|
138
161
|
|
|
139
162
|
end
|