ruby-saml 1.9.0 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- 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
|