ruby-saml 1.10.0 → 1.12.1
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 +28 -14
- data/README.md +96 -26
- data/changelog.md +37 -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 +71 -22
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -3
- 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 +68 -22
- 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 +29 -16
- data/lib/onelogin/ruby-saml/utils.rb +74 -1
- 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 -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 -1619
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -329
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -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
|
|
@@ -12,12 +12,13 @@ module OneLogin
|
|
|
12
12
|
# Auxiliary class to retrieve and parse the Identity Provider Metadata
|
|
13
13
|
#
|
|
14
14
|
class IdpMetadataParser
|
|
15
|
+
|
|
15
16
|
module SamlMetadata
|
|
16
17
|
module Vocabulary
|
|
17
|
-
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
18
|
-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
|
19
|
-
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
|
|
20
|
-
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
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
NAMESPACE = {
|
|
@@ -25,7 +26,7 @@ module OneLogin
|
|
|
25
26
|
"NameFormat" => Vocabulary::NAME_FORMAT,
|
|
26
27
|
"saml" => Vocabulary::SAML_ASSERTION,
|
|
27
28
|
"ds" => Vocabulary::DSIG
|
|
28
|
-
}
|
|
29
|
+
}.freeze
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
include SamlMetadata::Vocabulary
|
|
@@ -33,6 +34,16 @@ module OneLogin
|
|
|
33
34
|
attr_reader :response
|
|
34
35
|
attr_reader :options
|
|
35
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
|
+
|
|
36
47
|
# Parse the Identity Provider metadata and update the settings with the
|
|
37
48
|
# IdP values
|
|
38
49
|
#
|
|
@@ -102,6 +113,16 @@ module OneLogin
|
|
|
102
113
|
def parse(idp_metadata, options = {})
|
|
103
114
|
parsed_metadata = parse_to_hash(idp_metadata, options)
|
|
104
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
|
+
|
|
105
126
|
settings = options[:settings]
|
|
106
127
|
|
|
107
128
|
if settings.nil?
|
|
@@ -138,17 +159,21 @@ module OneLogin
|
|
|
138
159
|
#
|
|
139
160
|
# @return [Array<Hash>]
|
|
140
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 = {})
|
|
141
166
|
@document = REXML::Document.new(idp_metadata)
|
|
142
167
|
@options = options
|
|
143
168
|
|
|
144
|
-
idpsso_descriptors =
|
|
169
|
+
idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
|
|
145
170
|
if !idpsso_descriptors.any?
|
|
146
171
|
raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
|
|
147
172
|
end
|
|
148
173
|
|
|
149
|
-
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"])}
|
|
150
175
|
end
|
|
151
|
-
|
|
176
|
+
|
|
152
177
|
private
|
|
153
178
|
|
|
154
179
|
# Retrieve the remote IdP metadata from the URL or a cached copy.
|
|
@@ -174,6 +199,7 @@ module OneLogin
|
|
|
174
199
|
end
|
|
175
200
|
|
|
176
201
|
get = Net::HTTP::Get.new(uri.request_uri)
|
|
202
|
+
get.basic_auth uri.user, uri.password if uri.user
|
|
177
203
|
@response = http.request(get)
|
|
178
204
|
return response.body if response.is_a? Net::HTTPSuccess
|
|
179
205
|
|
|
@@ -183,15 +209,8 @@ module OneLogin
|
|
|
183
209
|
end
|
|
184
210
|
|
|
185
211
|
class IdpMetadata
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
REXML::XPath.match(
|
|
189
|
-
metadata_document,
|
|
190
|
-
path,
|
|
191
|
-
SamlMetadata::NAMESPACE
|
|
192
|
-
)
|
|
193
|
-
end
|
|
194
|
-
|
|
212
|
+
attr_reader :idpsso_descriptor, :entity_id
|
|
213
|
+
|
|
195
214
|
def initialize(idpsso_descriptor, entity_id)
|
|
196
215
|
@idpsso_descriptor = idpsso_descriptor
|
|
197
216
|
@entity_id = entity_id
|
|
@@ -201,12 +220,15 @@ module OneLogin
|
|
|
201
220
|
{
|
|
202
221
|
:idp_entity_id => @entity_id,
|
|
203
222
|
:name_identifier_format => idp_name_id_format,
|
|
204
|
-
:
|
|
205
|
-
:
|
|
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),
|
|
206
226
|
:idp_attribute_names => attribute_names,
|
|
207
227
|
:idp_cert => nil,
|
|
208
228
|
:idp_cert_fingerprint => nil,
|
|
209
|
-
:idp_cert_multi => nil
|
|
229
|
+
:idp_cert_multi => nil,
|
|
230
|
+
:valid_until => valid_until,
|
|
231
|
+
:cache_duration => cache_duration,
|
|
210
232
|
}.tap do |response_hash|
|
|
211
233
|
merge_certificates_into(response_hash) unless certificates.nil?
|
|
212
234
|
end
|
|
@@ -223,6 +245,20 @@ module OneLogin
|
|
|
223
245
|
Utils.element_text(node)
|
|
224
246
|
end
|
|
225
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
|
+
|
|
226
262
|
# @param binding_priority [Array]
|
|
227
263
|
# @return [String|nil] SingleSignOnService binding if exists
|
|
228
264
|
#
|
|
@@ -287,6 +323,21 @@ module OneLogin
|
|
|
287
323
|
return node.value if node
|
|
288
324
|
end
|
|
289
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
|
+
|
|
290
341
|
# @return [String|nil] Unformatted Certificate if exists
|
|
291
342
|
#
|
|
292
343
|
def certificates
|
|
@@ -384,8 +435,6 @@ module OneLogin
|
|
|
384
435
|
|
|
385
436
|
settings
|
|
386
437
|
end
|
|
387
|
-
|
|
388
|
-
private_constant :SamlMetadata, :IdpMetadata
|
|
389
438
|
end
|
|
390
439
|
end
|
|
391
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
|
|
@@ -33,6 +38,7 @@ module OneLogin
|
|
|
33
38
|
params.each_pair do |key, value|
|
|
34
39
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
35
40
|
end
|
|
41
|
+
raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
|
36
42
|
@logout_url = settings.idp_slo_target_url + request_params
|
|
37
43
|
end
|
|
38
44
|
|
|
@@ -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.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
112
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_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,28 +847,59 @@ module OneLogin
|
|
|
821
847
|
end
|
|
822
848
|
|
|
823
849
|
if sig_elements.size != 1
|
|
850
|
+
if sig_elements.size == 0
|
|
851
|
+
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
|
852
|
+
else
|
|
853
|
+
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
|
854
|
+
end
|
|
824
855
|
return append_error(error_msg)
|
|
825
856
|
end
|
|
826
857
|
|
|
858
|
+
old_errors = @errors.clone
|
|
859
|
+
|
|
827
860
|
idp_certs = settings.get_idp_cert_multi
|
|
828
861
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
|
829
862
|
opts = {}
|
|
830
863
|
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
|
|
831
|
-
|
|
864
|
+
idp_cert = settings.get_idp_cert
|
|
832
865
|
fingerprint = settings.get_fingerprint
|
|
866
|
+
opts[:cert] = idp_cert
|
|
833
867
|
|
|
834
|
-
|
|
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
|
|
835
876
|
return append_error(error_msg)
|
|
836
877
|
end
|
|
837
878
|
else
|
|
838
879
|
valid = false
|
|
880
|
+
expired = false
|
|
839
881
|
idp_certs[:signing].each do |idp_cert|
|
|
840
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
|
882
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
|
841
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
|
|
842
892
|
break
|
|
843
893
|
end
|
|
894
|
+
|
|
895
|
+
end
|
|
896
|
+
if expired
|
|
897
|
+
error_msg = "IdP x509 certificate expired"
|
|
898
|
+
return append_error(error_msg)
|
|
844
899
|
end
|
|
845
900
|
unless valid
|
|
901
|
+
# Remove duplicated errors
|
|
902
|
+
@errors = @errors.uniq
|
|
846
903
|
return append_error(error_msg)
|
|
847
904
|
end
|
|
848
905
|
end
|
|
@@ -943,17 +1000,6 @@ module OneLogin
|
|
|
943
1000
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
|
944
1001
|
end
|
|
945
1002
|
|
|
946
|
-
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
|
947
|
-
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
|
948
|
-
#
|
|
949
|
-
def assertion_encrypted?
|
|
950
|
-
! REXML::XPath.first(
|
|
951
|
-
document,
|
|
952
|
-
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
|
953
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
954
|
-
).nil?
|
|
955
|
-
end
|
|
956
|
-
|
|
957
1003
|
# Decrypts an EncryptedAssertion element
|
|
958
1004
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
|
959
1005
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|