ruby-saml 1.10.1 → 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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +7 -7
- data/.travis.yml +21 -20
- data/README.md +95 -26
- data/changelog.md +40 -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 +70 -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 +72 -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 +161 -383
- 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 -587
- 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 -329
- 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
@@ -3,6 +3,7 @@ require "rexml/document"
|
|
3
3
|
require "onelogin/ruby-saml/logging"
|
4
4
|
require "onelogin/ruby-saml/saml_message"
|
5
5
|
require "onelogin/ruby-saml/utils"
|
6
|
+
require "onelogin/ruby-saml/setting_error"
|
6
7
|
|
7
8
|
# Only supports SAML 2.0
|
8
9
|
module OneLogin
|
@@ -23,6 +24,10 @@ module OneLogin
|
|
23
24
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
24
25
|
end
|
25
26
|
|
27
|
+
def request_id
|
28
|
+
@uuid
|
29
|
+
end
|
30
|
+
|
26
31
|
# Creates the AuthNRequest string.
|
27
32
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
28
33
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
@@ -30,14 +35,14 @@ module OneLogin
|
|
30
35
|
#
|
31
36
|
def create(settings, params = {})
|
32
37
|
params = create_params(settings, params)
|
33
|
-
params_prefix = (settings.
|
38
|
+
params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
|
34
39
|
saml_request = CGI.escape(params.delete("SAMLRequest"))
|
35
40
|
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
|
36
41
|
params.each_pair do |key, value|
|
37
42
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
38
43
|
end
|
39
|
-
raise "Invalid settings,
|
40
|
-
@login_url = settings.
|
44
|
+
raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
|
45
|
+
@login_url = settings.idp_sso_service_url + request_params
|
41
46
|
end
|
42
47
|
|
43
48
|
# Creates the Get parameters for the request.
|
@@ -107,7 +112,7 @@ module OneLogin
|
|
107
112
|
root.attributes['ID'] = uuid
|
108
113
|
root.attributes['IssueInstant'] = time
|
109
114
|
root.attributes['Version'] = "2.0"
|
110
|
-
root.attributes['Destination'] = settings.
|
115
|
+
root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
|
111
116
|
root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
|
112
117
|
root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
|
113
118
|
root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?
|
@@ -117,9 +122,9 @@ module OneLogin
|
|
117
122
|
if settings.assertion_consumer_service_url != nil
|
118
123
|
root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url
|
119
124
|
end
|
120
|
-
if settings.
|
125
|
+
if settings.sp_entity_id != nil
|
121
126
|
issuer = root.add_element "saml:Issuer"
|
122
|
-
issuer.text = settings.
|
127
|
+
issuer.text = settings.sp_entity_id
|
123
128
|
end
|
124
129
|
|
125
130
|
if settings.name_identifier_value_requested != nil
|
@@ -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,12 +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
|
-
:idp_cert_multi => nil
|
229
|
+
:idp_cert_multi => nil,
|
230
|
+
:valid_until => valid_until,
|
231
|
+
:cache_duration => cache_duration,
|
211
232
|
}.tap do |response_hash|
|
212
233
|
merge_certificates_into(response_hash) unless certificates.nil?
|
213
234
|
end
|
@@ -224,6 +245,20 @@ module OneLogin
|
|
224
245
|
Utils.element_text(node)
|
225
246
|
end
|
226
247
|
|
248
|
+
# @return [String|nil] 'validUntil' attribute of metadata
|
249
|
+
#
|
250
|
+
def valid_until
|
251
|
+
root = @idpsso_descriptor.root
|
252
|
+
root.attributes['validUntil'] if root && root.attributes
|
253
|
+
end
|
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
|
+
|
227
262
|
# @param binding_priority [Array]
|
228
263
|
# @return [String|nil] SingleSignOnService binding if exists
|
229
264
|
#
|
@@ -288,6 +323,21 @@ module OneLogin
|
|
288
323
|
return node.value if node
|
289
324
|
end
|
290
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
|
+
|
291
341
|
# @return [String|nil] Unformatted Certificate if exists
|
292
342
|
#
|
293
343
|
def certificates
|
@@ -385,10 +435,6 @@ module OneLogin
|
|
385
435
|
|
386
436
|
settings
|
387
437
|
end
|
388
|
-
|
389
|
-
if self.respond_to?(:private_constant)
|
390
|
-
private_constant :SamlMetadata, :IdpMetadata
|
391
|
-
end
|
392
438
|
end
|
393
439
|
end
|
394
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
|