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
|
@@ -31,8 +31,9 @@ module OneLogin
|
|
|
31
31
|
|
|
32
32
|
# IdP Data
|
|
33
33
|
attr_accessor :idp_entity_id
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
attr_writer :idp_sso_service_url
|
|
35
|
+
attr_writer :idp_slo_service_url
|
|
36
|
+
attr_accessor :idp_slo_response_service_url
|
|
36
37
|
attr_accessor :idp_cert
|
|
37
38
|
attr_accessor :idp_cert_fingerprint
|
|
38
39
|
attr_accessor :idp_cert_fingerprint_algorithm
|
|
@@ -41,8 +42,10 @@ module OneLogin
|
|
|
41
42
|
attr_accessor :idp_name_qualifier
|
|
42
43
|
attr_accessor :valid_until
|
|
43
44
|
# SP Data
|
|
45
|
+
attr_writer :sp_entity_id
|
|
44
46
|
attr_accessor :assertion_consumer_service_url
|
|
45
|
-
|
|
47
|
+
attr_reader :assertion_consumer_service_binding
|
|
48
|
+
attr_writer :single_logout_service_url
|
|
46
49
|
attr_accessor :sp_name_qualifier
|
|
47
50
|
attr_accessor :name_identifier_format
|
|
48
51
|
attr_accessor :name_identifier_value
|
|
@@ -51,8 +54,9 @@ module OneLogin
|
|
|
51
54
|
attr_accessor :compress_request
|
|
52
55
|
attr_accessor :compress_response
|
|
53
56
|
attr_accessor :double_quote_xml_attribute_values
|
|
57
|
+
attr_accessor :message_max_bytesize
|
|
54
58
|
attr_accessor :passive
|
|
55
|
-
|
|
59
|
+
attr_reader :protocol_binding
|
|
56
60
|
attr_accessor :attributes_index
|
|
57
61
|
attr_accessor :force_authn
|
|
58
62
|
attr_accessor :certificate
|
|
@@ -65,74 +69,99 @@ module OneLogin
|
|
|
65
69
|
# Work-flow
|
|
66
70
|
attr_accessor :security
|
|
67
71
|
attr_accessor :soft
|
|
68
|
-
#
|
|
72
|
+
# Deprecated
|
|
69
73
|
attr_accessor :assertion_consumer_logout_service_url
|
|
70
|
-
|
|
74
|
+
attr_reader :assertion_consumer_logout_service_binding
|
|
71
75
|
attr_accessor :issuer
|
|
76
|
+
attr_accessor :idp_sso_target_url
|
|
77
|
+
attr_accessor :idp_slo_target_url
|
|
78
|
+
|
|
79
|
+
# @return [String] IdP Single Sign On Service URL
|
|
80
|
+
#
|
|
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
|
|
72
116
|
|
|
73
117
|
# @return [String] SP Entity ID
|
|
74
118
|
#
|
|
75
119
|
def sp_entity_id
|
|
76
|
-
|
|
77
|
-
if @sp_entity_id.nil?
|
|
78
|
-
if @issuer
|
|
79
|
-
val = @issuer
|
|
80
|
-
end
|
|
81
|
-
else
|
|
82
|
-
val = @sp_entity_id
|
|
83
|
-
end
|
|
84
|
-
val
|
|
120
|
+
@sp_entity_id || @issuer
|
|
85
121
|
end
|
|
86
122
|
|
|
87
|
-
# Setter for SP
|
|
88
|
-
# @param
|
|
123
|
+
# Setter for SP Protocol Binding
|
|
124
|
+
# @param value [String, Symbol].
|
|
89
125
|
#
|
|
90
|
-
def
|
|
91
|
-
@
|
|
126
|
+
def protocol_binding=(value)
|
|
127
|
+
@protocol_binding = get_binding(value)
|
|
92
128
|
end
|
|
93
129
|
|
|
94
|
-
#
|
|
130
|
+
# Setter for SP Assertion Consumer Service Binding
|
|
131
|
+
# @param value [String, Symbol].
|
|
95
132
|
#
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
if @single_logout_service_url.nil?
|
|
99
|
-
if @assertion_consumer_logout_service_url
|
|
100
|
-
val = @assertion_consumer_logout_service_url
|
|
101
|
-
end
|
|
102
|
-
else
|
|
103
|
-
val = @single_logout_service_url
|
|
104
|
-
end
|
|
105
|
-
val
|
|
133
|
+
def assertion_consumer_service_binding=(value)
|
|
134
|
+
@assertion_consumer_service_binding = get_binding(value)
|
|
106
135
|
end
|
|
107
136
|
|
|
108
|
-
#
|
|
109
|
-
# @param url [String].
|
|
137
|
+
# @return [String] Single Logout Service URL.
|
|
110
138
|
#
|
|
111
|
-
def single_logout_service_url
|
|
112
|
-
@single_logout_service_url
|
|
139
|
+
def single_logout_service_url
|
|
140
|
+
@single_logout_service_url || @assertion_consumer_logout_service_url
|
|
113
141
|
end
|
|
114
142
|
|
|
115
143
|
# @return [String] Single Logout Service Binding.
|
|
116
144
|
#
|
|
117
145
|
def single_logout_service_binding
|
|
118
|
-
|
|
119
|
-
if @single_logout_service_binding.nil?
|
|
120
|
-
if @assertion_consumer_logout_service_binding
|
|
121
|
-
val = @assertion_consumer_logout_service_binding
|
|
122
|
-
end
|
|
123
|
-
else
|
|
124
|
-
val = @single_logout_service_binding
|
|
125
|
-
end
|
|
126
|
-
val
|
|
146
|
+
@single_logout_service_binding || @assertion_consumer_logout_service_binding
|
|
127
147
|
end
|
|
128
148
|
|
|
129
149
|
# Setter for Single Logout Service Binding.
|
|
130
150
|
#
|
|
131
151
|
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
|
132
|
-
# @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]
|
|
133
162
|
#
|
|
134
|
-
def
|
|
135
|
-
@
|
|
163
|
+
def assertion_consumer_logout_service_binding=(value)
|
|
164
|
+
@assertion_consumer_logout_service_binding = get_binding(value)
|
|
136
165
|
end
|
|
137
166
|
|
|
138
167
|
# Calculates the fingerprint of the IdP x509 certificate.
|
|
@@ -220,12 +249,23 @@ module OneLogin
|
|
|
220
249
|
|
|
221
250
|
private
|
|
222
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
|
+
|
|
223
262
|
DEFAULTS = {
|
|
224
|
-
:assertion_consumer_service_binding =>
|
|
225
|
-
:single_logout_service_binding =>
|
|
263
|
+
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
|
|
264
|
+
:single_logout_service_binding => Utils::BINDINGS[:redirect],
|
|
226
265
|
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
|
227
266
|
:compress_request => true,
|
|
228
267
|
:compress_response => true,
|
|
268
|
+
:message_max_bytesize => 250000,
|
|
229
269
|
:soft => true,
|
|
230
270
|
:double_quote_xml_attribute_values => false,
|
|
231
271
|
:security => {
|
|
@@ -236,7 +276,7 @@ module OneLogin
|
|
|
236
276
|
:want_assertions_encrypted => false,
|
|
237
277
|
:want_name_id => false,
|
|
238
278
|
:metadata_signed => false,
|
|
239
|
-
:embed_sign => false,
|
|
279
|
+
:embed_sign => false, # Deprecated
|
|
240
280
|
:digest_method => XMLSecurity::Document::SHA1,
|
|
241
281
|
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
|
242
282
|
:check_idp_cert_expiration => false,
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,39 +103,44 @@ 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)
|
|
100
|
-
document = create_xml_document(settings, request_id, logout_message)
|
|
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)
|
|
101
111
|
sign_document(document, settings)
|
|
102
112
|
end
|
|
103
113
|
|
|
104
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
|
105
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
106
116
|
|
|
107
117
|
response_doc = XMLSecurity::Document.new
|
|
108
118
|
response_doc.uuid = uuid
|
|
109
119
|
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
|
121
|
+
|
|
122
|
+
|
|
110
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" }
|
|
111
124
|
root.attributes['ID'] = uuid
|
|
112
125
|
root.attributes['IssueInstant'] = time
|
|
113
126
|
root.attributes['Version'] = '2.0'
|
|
114
127
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
|
115
|
-
root.attributes['Destination'] =
|
|
128
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
|
116
129
|
|
|
117
130
|
if settings.sp_entity_id != nil
|
|
118
131
|
issuer = root.add_element "saml:Issuer"
|
|
119
132
|
issuer.text = settings.sp_entity_id
|
|
120
133
|
end
|
|
121
134
|
|
|
122
|
-
# add
|
|
135
|
+
# add status
|
|
123
136
|
status = root.add_element 'samlp:Status'
|
|
124
137
|
|
|
125
|
-
#
|
|
126
|
-
status_code
|
|
127
|
-
|
|
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
|
|
128
142
|
|
|
129
|
-
#
|
|
143
|
+
# status message
|
|
130
144
|
logout_message ||= 'Successfully Signed Out'
|
|
131
145
|
status_message = status.add_element 'samlp:StatusMessage'
|
|
132
146
|
status_message.text = logout_message
|
|
@@ -136,7 +150,7 @@ module OneLogin
|
|
|
136
150
|
|
|
137
151
|
def sign_document(document, settings)
|
|
138
152
|
# embed signature
|
|
139
|
-
if settings.
|
|
153
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
|
|
140
154
|
private_key = settings.get_sp_key
|
|
141
155
|
cert = settings.get_sp_cert
|
|
142
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
@@ -13,8 +13,25 @@ module OneLogin
|
|
|
13
13
|
class Utils
|
|
14
14
|
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
|
17
|
+
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
|
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
|
19
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
|
|
20
|
+
DURATION_FORMAT = %r(^
|
|
21
|
+
(-?)P # 1: Duration sign
|
|
22
|
+
(?:
|
|
23
|
+
(?:(\d+)Y)? # 2: Years
|
|
24
|
+
(?:(\d+)M)? # 3: Months
|
|
25
|
+
(?:(\d+)D)? # 4: Days
|
|
26
|
+
(?:T
|
|
27
|
+
(?:(\d+)H)? # 5: Hours
|
|
28
|
+
(?:(\d+)M)? # 6: Minutes
|
|
29
|
+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
|
|
30
|
+
)?
|
|
31
|
+
|
|
|
32
|
+
(\d+)W # 8: Weeks
|
|
33
|
+
)
|
|
34
|
+
$)x.freeze
|
|
18
35
|
|
|
19
36
|
# Checks if the x509 cert provided is expired
|
|
20
37
|
#
|
|
@@ -28,6 +45,37 @@ module OneLogin
|
|
|
28
45
|
return cert.not_after < Time.now
|
|
29
46
|
end
|
|
30
47
|
|
|
48
|
+
# Interprets a ISO8601 duration value relative to a given timestamp.
|
|
49
|
+
#
|
|
50
|
+
# @param duration [String] The duration, as a string.
|
|
51
|
+
# @param timestamp [Integer] The unix timestamp we should apply the
|
|
52
|
+
# duration to. Optional, default to the
|
|
53
|
+
# current time.
|
|
54
|
+
#
|
|
55
|
+
# @return [Integer] The new timestamp, after the duration is applied.
|
|
56
|
+
#
|
|
57
|
+
def self.parse_duration(duration, timestamp=Time.now.utc)
|
|
58
|
+
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
|
|
59
|
+
|
|
60
|
+
matches = duration.match(DURATION_FORMAT)
|
|
61
|
+
|
|
62
|
+
if matches.nil?
|
|
63
|
+
raise Exception.new("Invalid ISO 8601 duration")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sign = matches[1] == '-' ? -1 : 1
|
|
67
|
+
|
|
68
|
+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
|
69
|
+
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
|
|
70
|
+
|
|
71
|
+
initial_datetime = Time.at(timestamp).utc.to_datetime
|
|
72
|
+
final_datetime = initial_datetime.next_year(durYears)
|
|
73
|
+
final_datetime = final_datetime.next_month(durMonths)
|
|
74
|
+
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
|
|
75
|
+
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
|
76
|
+
return final_timestamp
|
|
77
|
+
end
|
|
78
|
+
|
|
31
79
|
# Return a properly formatted x509 certificate
|
|
32
80
|
#
|
|
33
81
|
# @param cert [String] The original certificate
|
|
@@ -253,6 +301,9 @@ module OneLogin
|
|
|
253
301
|
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
|
254
302
|
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
|
255
303
|
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
|
304
|
+
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
|
|
305
|
+
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
|
|
306
|
+
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
|
|
256
307
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
|
257
308
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
|
258
309
|
end
|
|
@@ -263,6 +314,16 @@ module OneLogin
|
|
|
263
314
|
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
|
264
315
|
assertion_plaintext = cipher.update(data)
|
|
265
316
|
assertion_plaintext << cipher.final
|
|
317
|
+
elsif auth_cipher
|
|
318
|
+
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
|
|
319
|
+
data = cipher_text[iv_len..text_len-1-tag_len]
|
|
320
|
+
auth_cipher.padding = 0
|
|
321
|
+
auth_cipher.key = symmetric_key
|
|
322
|
+
auth_cipher.iv = cipher_text[0..iv_len-1]
|
|
323
|
+
auth_cipher.auth_data = ''
|
|
324
|
+
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
|
|
325
|
+
assertion_plaintext = auth_cipher.update(data)
|
|
326
|
+
assertion_plaintext << auth_cipher.final
|
|
266
327
|
elsif rsa
|
|
267
328
|
rsa.private_decrypt(cipher_text)
|
|
268
329
|
elsif oaep
|