ruby-saml 1.10.2 → 1.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +21 -20
- data/README.md +95 -26
- data/changelog.md +30 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +11 -6
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +62 -24
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +11 -5
- data/lib/onelogin/ruby-saml/logoutresponse.rb +21 -2
- data/lib/onelogin/ruby-saml/metadata.rb +11 -3
- data/lib/onelogin/ruby-saml/response.rb +64 -23
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -0
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +71 -7
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +20 -1
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
- data/lib/onelogin/ruby-saml/utils.rb +69 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +34 -6
- data/ruby-saml.gemspec +9 -5
- metadata +36 -282
- 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 -409
- 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 -1620
- 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 -330
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -331
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
|
@@ -15,10 +15,10 @@ module OneLogin
|
|
|
15
15
|
|
|
16
16
|
module SamlMetadata
|
|
17
17
|
module Vocabulary
|
|
18
|
-
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
19
|
-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
|
20
|
-
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
|
|
21
|
-
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
18
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata".freeze
|
|
19
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
|
20
|
+
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*".freeze
|
|
21
|
+
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
NAMESPACE = {
|
|
@@ -26,7 +26,7 @@ module OneLogin
|
|
|
26
26
|
"NameFormat" => Vocabulary::NAME_FORMAT,
|
|
27
27
|
"saml" => Vocabulary::SAML_ASSERTION,
|
|
28
28
|
"ds" => Vocabulary::DSIG
|
|
29
|
-
}
|
|
29
|
+
}.freeze
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
include SamlMetadata::Vocabulary
|
|
@@ -34,6 +34,16 @@ module OneLogin
|
|
|
34
34
|
attr_reader :response
|
|
35
35
|
attr_reader :options
|
|
36
36
|
|
|
37
|
+
# fetch IdP descriptors from a metadata document
|
|
38
|
+
def self.get_idps(metadata_document, only_entity_id=nil)
|
|
39
|
+
path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
|
|
40
|
+
REXML::XPath.match(
|
|
41
|
+
metadata_document,
|
|
42
|
+
path,
|
|
43
|
+
SamlMetadata::NAMESPACE
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
37
47
|
# Parse the Identity Provider metadata and update the settings with the
|
|
38
48
|
# IdP values
|
|
39
49
|
#
|
|
@@ -103,6 +113,16 @@ module OneLogin
|
|
|
103
113
|
def parse(idp_metadata, options = {})
|
|
104
114
|
parsed_metadata = parse_to_hash(idp_metadata, options)
|
|
105
115
|
|
|
116
|
+
unless parsed_metadata[:cache_duration].nil?
|
|
117
|
+
cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
|
|
118
|
+
if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
|
|
119
|
+
parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
# Remove the cache_duration because on the settings
|
|
123
|
+
# we only gonna suppot valid_until
|
|
124
|
+
parsed_metadata.delete(:cache_duration)
|
|
125
|
+
|
|
106
126
|
settings = options[:settings]
|
|
107
127
|
|
|
108
128
|
if settings.nil?
|
|
@@ -139,17 +159,21 @@ module OneLogin
|
|
|
139
159
|
#
|
|
140
160
|
# @return [Array<Hash>]
|
|
141
161
|
def parse_to_array(idp_metadata, options = {})
|
|
162
|
+
parse_to_idp_metadata_array(idp_metadata, options).map{|idp_md| idp_md.to_hash(options)}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def parse_to_idp_metadata_array(idp_metadata, options = {})
|
|
142
166
|
@document = REXML::Document.new(idp_metadata)
|
|
143
167
|
@options = options
|
|
144
168
|
|
|
145
|
-
idpsso_descriptors =
|
|
169
|
+
idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
|
|
146
170
|
if !idpsso_descriptors.any?
|
|
147
171
|
raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
|
|
148
172
|
end
|
|
149
173
|
|
|
150
|
-
return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"])
|
|
174
|
+
return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
|
|
151
175
|
end
|
|
152
|
-
|
|
176
|
+
|
|
153
177
|
private
|
|
154
178
|
|
|
155
179
|
# Retrieve the remote IdP metadata from the URL or a cached copy.
|
|
@@ -175,6 +199,7 @@ module OneLogin
|
|
|
175
199
|
end
|
|
176
200
|
|
|
177
201
|
get = Net::HTTP::Get.new(uri.request_uri)
|
|
202
|
+
get.basic_auth uri.user, uri.password if uri.user
|
|
178
203
|
@response = http.request(get)
|
|
179
204
|
return response.body if response.is_a? Net::HTTPSuccess
|
|
180
205
|
|
|
@@ -184,15 +209,8 @@ module OneLogin
|
|
|
184
209
|
end
|
|
185
210
|
|
|
186
211
|
class IdpMetadata
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
REXML::XPath.match(
|
|
190
|
-
metadata_document,
|
|
191
|
-
path,
|
|
192
|
-
SamlMetadata::NAMESPACE
|
|
193
|
-
)
|
|
194
|
-
end
|
|
195
|
-
|
|
212
|
+
attr_reader :idpsso_descriptor, :entity_id
|
|
213
|
+
|
|
196
214
|
def initialize(idpsso_descriptor, entity_id)
|
|
197
215
|
@idpsso_descriptor = idpsso_descriptor
|
|
198
216
|
@entity_id = entity_id
|
|
@@ -202,13 +220,15 @@ module OneLogin
|
|
|
202
220
|
{
|
|
203
221
|
:idp_entity_id => @entity_id,
|
|
204
222
|
:name_identifier_format => idp_name_id_format,
|
|
205
|
-
:
|
|
206
|
-
:
|
|
223
|
+
:idp_sso_service_url => single_signon_service_url(options),
|
|
224
|
+
:idp_slo_service_url => single_logout_service_url(options),
|
|
225
|
+
:idp_slo_response_service_url => single_logout_response_service_url(options),
|
|
207
226
|
:idp_attribute_names => attribute_names,
|
|
208
227
|
:idp_cert => nil,
|
|
209
228
|
:idp_cert_fingerprint => nil,
|
|
210
229
|
:idp_cert_multi => nil,
|
|
211
|
-
:valid_until => valid_until
|
|
230
|
+
:valid_until => valid_until,
|
|
231
|
+
:cache_duration => cache_duration,
|
|
212
232
|
}.tap do |response_hash|
|
|
213
233
|
merge_certificates_into(response_hash) unless certificates.nil?
|
|
214
234
|
end
|
|
@@ -232,6 +252,13 @@ module OneLogin
|
|
|
232
252
|
root.attributes['validUntil'] if root && root.attributes
|
|
233
253
|
end
|
|
234
254
|
|
|
255
|
+
# @return [String|nil] 'cacheDuration' attribute of metadata
|
|
256
|
+
#
|
|
257
|
+
def cache_duration
|
|
258
|
+
root = @idpsso_descriptor.root
|
|
259
|
+
root.attributes['cacheDuration'] if root && root.attributes
|
|
260
|
+
end
|
|
261
|
+
|
|
235
262
|
# @param binding_priority [Array]
|
|
236
263
|
# @return [String|nil] SingleSignOnService binding if exists
|
|
237
264
|
#
|
|
@@ -296,6 +323,21 @@ module OneLogin
|
|
|
296
323
|
return node.value if node
|
|
297
324
|
end
|
|
298
325
|
|
|
326
|
+
# @param options [Hash]
|
|
327
|
+
# @return [String|nil] SingleLogoutService response url if exists
|
|
328
|
+
#
|
|
329
|
+
def single_logout_response_service_url(options = {})
|
|
330
|
+
binding = single_logout_service_binding(options[:slo_binding])
|
|
331
|
+
return if binding.nil?
|
|
332
|
+
|
|
333
|
+
node = REXML::XPath.first(
|
|
334
|
+
@idpsso_descriptor,
|
|
335
|
+
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
|
|
336
|
+
SamlMetadata::NAMESPACE
|
|
337
|
+
)
|
|
338
|
+
return node.value if node
|
|
339
|
+
end
|
|
340
|
+
|
|
299
341
|
# @return [String|nil] Unformatted Certificate if exists
|
|
300
342
|
#
|
|
301
343
|
def certificates
|
|
@@ -393,10 +435,6 @@ module OneLogin
|
|
|
393
435
|
|
|
394
436
|
settings
|
|
395
437
|
end
|
|
396
|
-
|
|
397
|
-
if self.respond_to?(:private_constant)
|
|
398
|
-
private_constant :SamlMetadata, :IdpMetadata
|
|
399
|
-
end
|
|
400
438
|
end
|
|
401
439
|
end
|
|
402
440
|
end
|
|
@@ -8,9 +8,9 @@ module OneLogin
|
|
|
8
8
|
|
|
9
9
|
def self.logger
|
|
10
10
|
@logger ||= begin
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
(defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
|
12
|
+
DEFAULT_LOGGER
|
|
13
|
+
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.logger=(logger)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "onelogin/ruby-saml/logging"
|
|
2
2
|
require "onelogin/ruby-saml/saml_message"
|
|
3
3
|
require "onelogin/ruby-saml/utils"
|
|
4
|
+
require "onelogin/ruby-saml/setting_error"
|
|
4
5
|
|
|
5
6
|
# Only supports SAML 2.0
|
|
6
7
|
module OneLogin
|
|
@@ -20,6 +21,10 @@ module OneLogin
|
|
|
20
21
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
def request_id
|
|
25
|
+
@uuid
|
|
26
|
+
end
|
|
27
|
+
|
|
23
28
|
# Creates the Logout Request string.
|
|
24
29
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
25
30
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
@@ -27,13 +32,14 @@ module OneLogin
|
|
|
27
32
|
#
|
|
28
33
|
def create(settings, params={})
|
|
29
34
|
params = create_params(settings, params)
|
|
30
|
-
params_prefix = (settings.
|
|
35
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
|
31
36
|
saml_request = CGI.escape(params.delete("SAMLRequest"))
|
|
32
37
|
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
|
|
33
38
|
params.each_pair do |key, value|
|
|
34
39
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
35
40
|
end
|
|
36
|
-
|
|
41
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
|
|
42
|
+
@logout_url = settings.idp_slo_service_url + request_params
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
# Creates the Get parameters for the logout request.
|
|
@@ -103,11 +109,11 @@ module OneLogin
|
|
|
103
109
|
root.attributes['ID'] = uuid
|
|
104
110
|
root.attributes['IssueInstant'] = time
|
|
105
111
|
root.attributes['Version'] = "2.0"
|
|
106
|
-
root.attributes['Destination'] = settings.
|
|
112
|
+
root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
|
|
107
113
|
|
|
108
|
-
if settings.
|
|
114
|
+
if settings.sp_entity_id
|
|
109
115
|
issuer = root.add_element "saml:Issuer"
|
|
110
|
-
issuer.text = settings.
|
|
116
|
+
issuer.text = settings.sp_entity_id
|
|
111
117
|
end
|
|
112
118
|
|
|
113
119
|
nameid = root.add_element "saml:NameID"
|
|
@@ -47,6 +47,10 @@ module OneLogin
|
|
|
47
47
|
@document = XMLSecurity::SignedDocument.new(@response)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def response_id
|
|
51
|
+
id(document)
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
# Checks if the Status has the "Success" code
|
|
51
55
|
# @return [Boolean] True if the StatusCode is Sucess
|
|
52
56
|
# @raise [ValidationError] if soft == false and validation fails
|
|
@@ -163,7 +167,7 @@ module OneLogin
|
|
|
163
167
|
|
|
164
168
|
return append_error("No settings on logout response") if settings.nil?
|
|
165
169
|
|
|
166
|
-
return append_error("No
|
|
170
|
+
return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil?
|
|
167
171
|
|
|
168
172
|
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
|
|
169
173
|
return append_error("No fingerprint or certificate on settings of the logout response")
|
|
@@ -228,13 +232,19 @@ module OneLogin
|
|
|
228
232
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
|
229
233
|
)
|
|
230
234
|
|
|
235
|
+
expired = false
|
|
231
236
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
232
237
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
|
233
|
-
:cert =>
|
|
238
|
+
:cert => idp_cert,
|
|
234
239
|
:sig_alg => options[:get_params]['SigAlg'],
|
|
235
240
|
:signature => options[:get_params]['Signature'],
|
|
236
241
|
:query_string => query_string
|
|
237
242
|
)
|
|
243
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
|
244
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
245
|
+
expired = true
|
|
246
|
+
end
|
|
247
|
+
end
|
|
238
248
|
else
|
|
239
249
|
valid = false
|
|
240
250
|
idp_certs[:signing].each do |signing_idp_cert|
|
|
@@ -245,11 +255,20 @@ module OneLogin
|
|
|
245
255
|
:query_string => query_string
|
|
246
256
|
)
|
|
247
257
|
if valid
|
|
258
|
+
if settings.security[:check_idp_cert_expiration]
|
|
259
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
|
260
|
+
expired = true
|
|
261
|
+
end
|
|
262
|
+
end
|
|
248
263
|
break
|
|
249
264
|
end
|
|
250
265
|
end
|
|
251
266
|
end
|
|
252
267
|
|
|
268
|
+
if expired
|
|
269
|
+
error_msg = "IdP x509 certificate expired"
|
|
270
|
+
return append_error(error_msg)
|
|
271
|
+
end
|
|
253
272
|
unless valid
|
|
254
273
|
error_msg = "Invalid Signature on Logout Response"
|
|
255
274
|
return append_error(error_msg)
|
|
@@ -15,9 +15,11 @@ module OneLogin
|
|
|
15
15
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
16
16
|
# @param pretty_print [Boolean] Pretty print or not the response
|
|
17
17
|
# (No pretty print if you gonna validate the signature)
|
|
18
|
+
# @param valid_until [DateTime] Metadata's valid time
|
|
19
|
+
# @param cache_duration [Integer] Duration of the cache in seconds
|
|
18
20
|
# @return [String] XML Metadata of the Service Provider
|
|
19
21
|
#
|
|
20
|
-
def generate(settings, pretty_print=false)
|
|
22
|
+
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
|
|
21
23
|
meta_doc = XMLSecurity::Document.new
|
|
22
24
|
namespaces = {
|
|
23
25
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
@@ -57,8 +59,14 @@ module OneLogin
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
|
60
|
-
if settings.
|
|
61
|
-
root.attributes["entityID"] = settings.
|
|
62
|
+
if settings.sp_entity_id
|
|
63
|
+
root.attributes["entityID"] = settings.sp_entity_id
|
|
64
|
+
end
|
|
65
|
+
if valid_until
|
|
66
|
+
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z')
|
|
67
|
+
end
|
|
68
|
+
if cache_duration
|
|
69
|
+
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S"
|
|
62
70
|
end
|
|
63
71
|
if settings.single_logout_service_url
|
|
64
72
|
sp_sso.add_element "md:SingleLogoutService", {
|
|
@@ -34,7 +34,7 @@ module OneLogin
|
|
|
34
34
|
# This is not a whitelist to allow people extending OneLogin::RubySaml:Response
|
|
35
35
|
# and pass custom options
|
|
36
36
|
AVAILABLE_OPTIONS = [
|
|
37
|
-
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
|
|
37
|
+
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
|
|
38
38
|
:skip_destination, :skip_recipient_check, :skip_subject_confirmation
|
|
39
39
|
]
|
|
40
40
|
# TODO: Update the comment on initialize to describe every option
|
|
@@ -47,6 +47,8 @@ module OneLogin
|
|
|
47
47
|
# or :matches_request_id that will validate that the response matches the ID of the request,
|
|
48
48
|
# or skip the subject confirmation validation with the :skip_subject_confirmation option
|
|
49
49
|
# or skip the recipient validation of the subject confirmation element with :skip_recipient_check option
|
|
50
|
+
# or skip the audience validation with :skip_audience option
|
|
51
|
+
#
|
|
50
52
|
def initialize(response, options = {})
|
|
51
53
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
|
52
54
|
|
|
@@ -341,6 +343,28 @@ module OneLogin
|
|
|
341
343
|
return options[:allowed_clock_drift].to_f
|
|
342
344
|
end
|
|
343
345
|
|
|
346
|
+
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
347
|
+
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
348
|
+
#
|
|
349
|
+
def assertion_encrypted?
|
|
350
|
+
! REXML::XPath.first(
|
|
351
|
+
document,
|
|
352
|
+
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
353
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
354
|
+
).nil?
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def response_id
|
|
358
|
+
id(document)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def assertion_id
|
|
362
|
+
@assertion_id ||= begin
|
|
363
|
+
node = xpath_first_from_signed_assertion("")
|
|
364
|
+
node.nil? ? nil : node.attributes['ID']
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
344
368
|
private
|
|
345
369
|
|
|
346
370
|
# Validates the SAML Response (calls several validation methods)
|
|
@@ -435,7 +459,7 @@ module OneLogin
|
|
|
435
459
|
# @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
|
|
436
460
|
#
|
|
437
461
|
def validate_id
|
|
438
|
-
unless
|
|
462
|
+
unless response_id
|
|
439
463
|
return append_error("Missing ID attribute on SAML Response")
|
|
440
464
|
end
|
|
441
465
|
|
|
@@ -584,16 +608,18 @@ module OneLogin
|
|
|
584
608
|
end
|
|
585
609
|
|
|
586
610
|
# Validates the Audience, (If the Audience match the Service Provider EntityID)
|
|
611
|
+
# If the response was initialized with the :skip_audience option, this validation is skipped,
|
|
587
612
|
# If fails, the error is added to the errors array
|
|
588
613
|
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
|
|
589
614
|
# @raise [ValidationError] if soft == false and validation fails
|
|
590
615
|
#
|
|
591
616
|
def validate_audience
|
|
592
|
-
return true if
|
|
617
|
+
return true if options[:skip_audience]
|
|
618
|
+
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
|
593
619
|
|
|
594
|
-
unless audiences.include? settings.
|
|
620
|
+
unless audiences.include? settings.sp_entity_id
|
|
595
621
|
s = audiences.count > 1 ? 's' : '';
|
|
596
|
-
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.
|
|
622
|
+
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
|
|
597
623
|
return append_error(error_msg)
|
|
598
624
|
end
|
|
599
625
|
|
|
@@ -781,8 +807,8 @@ module OneLogin
|
|
|
781
807
|
return append_error("An empty NameID value found")
|
|
782
808
|
end
|
|
783
809
|
|
|
784
|
-
unless settings.
|
|
785
|
-
if name_id_spnamequalifier != settings.
|
|
810
|
+
unless settings.sp_entity_id.nil? || settings.sp_entity_id.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
|
|
811
|
+
if name_id_spnamequalifier != settings.sp_entity_id
|
|
786
812
|
return append_error("The SPNameQualifier value mistmatch the SP entityID value.")
|
|
787
813
|
end
|
|
788
814
|
end
|
|
@@ -802,7 +828,7 @@ module OneLogin
|
|
|
802
828
|
# otherwise, review if the decrypted assertion contains a signature
|
|
803
829
|
sig_elements = REXML::XPath.match(
|
|
804
830
|
document,
|
|
805
|
-
"/p:Response[@ID=$id]/ds:Signature
|
|
831
|
+
"/p:Response[@ID=$id]/ds:Signature",
|
|
806
832
|
{ "p" => PROTOCOL, "ds" => DSIG },
|
|
807
833
|
{ 'id' => document.signed_element_id }
|
|
808
834
|
)
|
|
@@ -821,7 +847,7 @@ module OneLogin
|
|
|
821
847
|
end
|
|
822
848
|
|
|
823
849
|
if sig_elements.size != 1
|
|
824
|
-
if
|
|
850
|
+
if sig_elements.size == 0
|
|
825
851
|
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
|
826
852
|
else
|
|
827
853
|
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
|
@@ -829,25 +855,51 @@ module OneLogin
|
|
|
829
855
|
return append_error(error_msg)
|
|
830
856
|
end
|
|
831
857
|
|
|
858
|
+
old_errors = @errors.clone
|
|
859
|
+
|
|
832
860
|
idp_certs = settings.get_idp_cert_multi
|
|
833
861
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
834
862
|
opts = {}
|
|
835
863
|
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
|
|
836
|
-
|
|
864
|
+
idp_cert = settings.get_idp_cert
|
|
837
865
|
fingerprint = settings.get_fingerprint
|
|
866
|
+
opts[:cert] = idp_cert
|
|
838
867
|
|
|
839
|
-
|
|
868
|
+
if fingerprint && doc.validate_document(fingerprint, @soft, opts)
|
|
869
|
+
if settings.security[:check_idp_cert_expiration]
|
|
870
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
871
|
+
error_msg = "IdP x509 certificate expired"
|
|
872
|
+
return append_error(error_msg)
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
else
|
|
840
876
|
return append_error(error_msg)
|
|
841
877
|
end
|
|
842
878
|
else
|
|
843
879
|
valid = false
|
|
880
|
+
expired = false
|
|
844
881
|
idp_certs[:signing].each do |idp_cert|
|
|
845
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
|
882
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
|
846
883
|
if valid
|
|
884
|
+
if settings.security[:check_idp_cert_expiration]
|
|
885
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
|
886
|
+
expired = true
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
# At least one certificate is valid, restore the old accumulated errors
|
|
891
|
+
@errors = old_errors
|
|
847
892
|
break
|
|
848
893
|
end
|
|
894
|
+
|
|
895
|
+
end
|
|
896
|
+
if expired
|
|
897
|
+
error_msg = "IdP x509 certificate expired"
|
|
898
|
+
return append_error(error_msg)
|
|
849
899
|
end
|
|
850
900
|
unless valid
|
|
901
|
+
# Remove duplicated errors
|
|
902
|
+
@errors = @errors.uniq
|
|
851
903
|
return append_error(error_msg)
|
|
852
904
|
end
|
|
853
905
|
end
|
|
@@ -948,17 +1000,6 @@ module OneLogin
|
|
|
948
1000
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
|
949
1001
|
end
|
|
950
1002
|
|
|
951
|
-
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
952
|
-
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
953
|
-
#
|
|
954
|
-
def assertion_encrypted?
|
|
955
|
-
! REXML::XPath.first(
|
|
956
|
-
document,
|
|
957
|
-
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
958
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
959
|
-
).nil?
|
|
960
|
-
end
|
|
961
|
-
|
|
962
1003
|
# Decrypts an EncryptedAssertion element
|
|
963
1004
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
|
964
1005
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|
|
@@ -22,6 +22,8 @@ module OneLogin
|
|
|
22
22
|
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
|
|
23
23
|
@@mutex = Mutex.new
|
|
24
24
|
|
|
25
|
+
MAX_BYTE_SIZE = 250000
|
|
26
|
+
|
|
25
27
|
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
|
|
26
28
|
#
|
|
27
29
|
def self.schema
|
|
@@ -89,6 +91,10 @@ module OneLogin
|
|
|
89
91
|
def decode_raw_saml(saml)
|
|
90
92
|
return saml unless base64_encoded?(saml)
|
|
91
93
|
|
|
94
|
+
if saml.bytesize > MAX_BYTE_SIZE
|
|
95
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
|
|
96
|
+
end
|
|
97
|
+
|
|
92
98
|
decoded = decode(saml)
|
|
93
99
|
begin
|
|
94
100
|
inflate(decoded)
|
|
@@ -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,8 +31,10 @@ module OneLogin
|
|
|
30
31
|
|
|
31
32
|
# IdP Data
|
|
32
33
|
attr_accessor :idp_entity_id
|
|
33
|
-
|
|
34
|
-
attr_accessor :
|
|
34
|
+
|
|
35
|
+
attr_accessor :idp_sso_service_url
|
|
36
|
+
attr_accessor :idp_slo_service_url
|
|
37
|
+
attr_accessor :idp_slo_response_service_url
|
|
35
38
|
attr_accessor :idp_cert
|
|
36
39
|
attr_accessor :idp_cert_fingerprint
|
|
37
40
|
attr_accessor :idp_cert_fingerprint_algorithm
|
|
@@ -40,7 +43,6 @@ module OneLogin
|
|
|
40
43
|
attr_accessor :idp_name_qualifier
|
|
41
44
|
attr_accessor :valid_until
|
|
42
45
|
# SP Data
|
|
43
|
-
attr_accessor :issuer
|
|
44
46
|
attr_accessor :assertion_consumer_service_url
|
|
45
47
|
attr_accessor :assertion_consumer_service_binding
|
|
46
48
|
attr_accessor :sp_name_qualifier
|
|
@@ -68,6 +70,58 @@ module OneLogin
|
|
|
68
70
|
# Compability
|
|
69
71
|
attr_accessor :assertion_consumer_logout_service_url
|
|
70
72
|
attr_accessor :assertion_consumer_logout_service_binding
|
|
73
|
+
attr_accessor :issuer
|
|
74
|
+
attr_accessor :idp_sso_target_url
|
|
75
|
+
attr_accessor :idp_slo_target_url
|
|
76
|
+
|
|
77
|
+
# @return [String] IdP Single Sign On Service URL
|
|
78
|
+
#
|
|
79
|
+
def idp_sso_service_url
|
|
80
|
+
val = nil
|
|
81
|
+
if @idp_sso_service_url.nil?
|
|
82
|
+
if @idp_sso_target_url
|
|
83
|
+
val = @idp_sso_target_url
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
val = @idp_sso_service_url
|
|
87
|
+
end
|
|
88
|
+
val
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String] IdP Single Logout Service URL
|
|
92
|
+
#
|
|
93
|
+
def idp_slo_service_url
|
|
94
|
+
val = nil
|
|
95
|
+
if @idp_slo_service_url.nil?
|
|
96
|
+
if @idp_slo_target_url
|
|
97
|
+
val = @idp_slo_target_url
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
val = @idp_slo_service_url
|
|
101
|
+
end
|
|
102
|
+
val
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [String] SP Entity ID
|
|
106
|
+
#
|
|
107
|
+
def sp_entity_id
|
|
108
|
+
val = nil
|
|
109
|
+
if @sp_entity_id.nil?
|
|
110
|
+
if @issuer
|
|
111
|
+
val = @issuer
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
val = @sp_entity_id
|
|
115
|
+
end
|
|
116
|
+
val
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Setter for SP Entity ID.
|
|
120
|
+
# @param val [String].
|
|
121
|
+
#
|
|
122
|
+
def sp_entity_id=(val)
|
|
123
|
+
@sp_entity_id = val
|
|
124
|
+
end
|
|
71
125
|
|
|
72
126
|
# @return [String] Single Logout Service URL.
|
|
73
127
|
#
|
|
@@ -167,7 +221,15 @@ module OneLogin
|
|
|
167
221
|
return nil if certificate.nil? || certificate.empty?
|
|
168
222
|
|
|
169
223
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
|
170
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
|
224
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
|
225
|
+
|
|
226
|
+
if security[:check_sp_cert_expiration]
|
|
227
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
|
228
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
cert
|
|
171
233
|
end
|
|
172
234
|
|
|
173
235
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
|
@@ -197,6 +259,7 @@ module OneLogin
|
|
|
197
259
|
:compress_request => true,
|
|
198
260
|
:compress_response => true,
|
|
199
261
|
:soft => true,
|
|
262
|
+
:double_quote_xml_attribute_values => false,
|
|
200
263
|
:security => {
|
|
201
264
|
:authn_requests_signed => false,
|
|
202
265
|
:logout_requests_signed => false,
|
|
@@ -207,9 +270,10 @@ module OneLogin
|
|
|
207
270
|
:metadata_signed => false,
|
|
208
271
|
:embed_sign => false,
|
|
209
272
|
:digest_method => XMLSecurity::Document::SHA1,
|
|
210
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
|
211
|
-
|
|
212
|
-
|
|
273
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
|
274
|
+
:check_idp_cert_expiration => false,
|
|
275
|
+
:check_sp_cert_expiration => false
|
|
276
|
+
}.freeze
|
|
213
277
|
}.freeze
|
|
214
278
|
end
|
|
215
279
|
end
|