ruby-saml 0.8.16 → 0.9
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/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -89
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +34 -93
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -24
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +62 -322
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -121
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -61
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -84
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -144
- data/test/logoutresponse_test.rb +43 -25
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +103 -90
- data/test/response_test.rb +181 -471
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +5 -5
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +41 -44
- data/test/slo_logoutresponse_test.rb +87 -167
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +34 -84
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- 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/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.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/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.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/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- 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_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
require "xml_security"
|
|
2
2
|
require "time"
|
|
3
3
|
require "nokogiri"
|
|
4
|
-
require "onelogin/ruby-saml/utils"
|
|
5
|
-
require 'onelogin/ruby-saml/attributes'
|
|
6
4
|
|
|
7
5
|
# Only supports SAML 2.0
|
|
8
6
|
module OneLogin
|
|
9
7
|
module RubySaml
|
|
10
8
|
|
|
11
|
-
class Response
|
|
9
|
+
class Response < SamlMessage
|
|
12
10
|
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
13
11
|
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
14
12
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
|
15
13
|
|
|
16
14
|
# TODO: This should probably be ctor initialized too... WDYT?
|
|
17
15
|
attr_accessor :settings
|
|
16
|
+
attr_accessor :errors
|
|
18
17
|
|
|
19
18
|
attr_reader :options
|
|
20
19
|
attr_reader :response
|
|
21
20
|
attr_reader :document
|
|
22
21
|
|
|
23
22
|
def initialize(response, options = {})
|
|
23
|
+
@errors = []
|
|
24
24
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
|
25
25
|
@options = options
|
|
26
|
-
@response =
|
|
27
|
-
@document = XMLSecurity::SignedDocument.new(@response)
|
|
26
|
+
@response = decode_raw_saml(response)
|
|
27
|
+
@document = XMLSecurity::SignedDocument.new(@response, @errors)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def is_valid?
|
|
@@ -35,16 +35,18 @@ module OneLogin
|
|
|
35
35
|
validate(false)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def errors
|
|
39
|
+
@errors
|
|
40
|
+
end
|
|
41
|
+
|
|
38
42
|
# The value of the user identifier as designated by the initialization request response
|
|
39
43
|
def name_id
|
|
40
44
|
@name_id ||= begin
|
|
41
45
|
node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
|
42
|
-
|
|
46
|
+
node.nil? ? nil : node.text
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
alias nameid name_id
|
|
47
|
-
|
|
48
50
|
def sessionindex
|
|
49
51
|
@sessionindex ||= begin
|
|
50
52
|
node = xpath_first_from_signed_assertion('/a:AuthnStatement')
|
|
@@ -52,9 +54,9 @@ module OneLogin
|
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
+
# Returns OneLogin::RubySaml::Attributes enumerable collection.
|
|
57
58
|
# All attributes can be iterated over +attributes.each+ or returned as array by +attributes.all+
|
|
59
|
+
#
|
|
58
60
|
# For backwards compatibility ruby-saml returns by default only the first value for a given attribute with
|
|
59
61
|
# attributes['name']
|
|
60
62
|
# To get all of the attributes, use:
|
|
@@ -63,36 +65,24 @@ module OneLogin
|
|
|
63
65
|
# OneLogin::RubySaml::Attributes.single_value_compatibility = false
|
|
64
66
|
# Now this will return an array:
|
|
65
67
|
# attributes['name']
|
|
66
|
-
#
|
|
67
|
-
# @return [Attributes] OneLogin::RubySaml::Attributes enumerable collection.
|
|
68
|
-
#
|
|
69
68
|
def attributes
|
|
70
69
|
@attr_statements ||= begin
|
|
71
70
|
attributes = Attributes.new
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# NameQualifier, if present is prefixed with a "/" to the value
|
|
86
|
-
else
|
|
87
|
-
REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
|
|
88
|
-
(n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + Utils.element_text(n)
|
|
89
|
-
}
|
|
90
|
-
end
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
attributes.add(name, values.flatten)
|
|
94
|
-
end
|
|
72
|
+
stmt_element = xpath_first_from_signed_assertion('/a:AttributeStatement')
|
|
73
|
+
return attributes if stmt_element.nil?
|
|
74
|
+
|
|
75
|
+
stmt_element.elements.each do |attr_element|
|
|
76
|
+
name = attr_element.attributes["Name"]
|
|
77
|
+
values = attr_element.elements.collect{|e|
|
|
78
|
+
# SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
|
|
79
|
+
# otherwise the value is to be regarded as empty.
|
|
80
|
+
["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
attributes.add(name, values)
|
|
95
84
|
end
|
|
85
|
+
|
|
96
86
|
attributes
|
|
97
87
|
end
|
|
98
88
|
end
|
|
@@ -113,6 +103,13 @@ module OneLogin
|
|
|
113
103
|
end
|
|
114
104
|
end
|
|
115
105
|
|
|
106
|
+
def status_message
|
|
107
|
+
@status_message ||= begin
|
|
108
|
+
node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
109
|
+
node.text if node
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
116
113
|
# Conditions (if any) for the assertion to run
|
|
117
114
|
def conditions
|
|
118
115
|
@conditions ||= xpath_first_from_signed_assertion('/a:Conditions')
|
|
@@ -130,223 +127,43 @@ module OneLogin
|
|
|
130
127
|
@issuer ||= begin
|
|
131
128
|
node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
132
129
|
node ||= xpath_first_from_signed_assertion('/a:Issuer')
|
|
133
|
-
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Gets the Issuers (from Response and Assertion).
|
|
138
|
-
# (returns the first node that matches the supplied xpath from the Response and from the Assertion)
|
|
139
|
-
# @return [Array] Array with the Issuers (REXML::Element)
|
|
140
|
-
#
|
|
141
|
-
def issuers
|
|
142
|
-
@issuers ||= begin
|
|
143
|
-
issuer_response_nodes = REXML::XPath.match(
|
|
144
|
-
document,
|
|
145
|
-
"/p:Response/a:Issuer",
|
|
146
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
unless issuer_response_nodes.size == 1
|
|
150
|
-
error_msg = "Issuer of the Response not found or multiple."
|
|
151
|
-
raise ValidationError.new(error_msg)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
|
|
155
|
-
unless issuer_assertion_nodes.size == 1
|
|
156
|
-
error_msg = "Issuer of the Assertion not found or multiple."
|
|
157
|
-
raise ValidationError.new(error_msg)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
nodes = issuer_response_nodes + issuer_assertion_nodes
|
|
161
|
-
nodes.map { |node| Utils.element_text(node) }.compact.uniq
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# @return [Array] The Audience elements from the Contitions of the SAML Response.
|
|
166
|
-
#
|
|
167
|
-
def audiences
|
|
168
|
-
@audiences ||= begin
|
|
169
|
-
nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
|
|
170
|
-
nodes.map { |node| Utils.element_text(node) }.reject(&:empty?)
|
|
130
|
+
node.nil? ? nil : node.text
|
|
171
131
|
end
|
|
172
132
|
end
|
|
173
133
|
|
|
174
134
|
private
|
|
175
135
|
|
|
176
|
-
def validation_error(message)
|
|
177
|
-
raise ValidationError.new(message)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
136
|
def validate(soft = true)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
validate_audience(soft) &&
|
|
188
|
-
validate_issuer(soft) &&
|
|
189
|
-
validate_signature(soft) &&
|
|
190
|
-
success?
|
|
137
|
+
valid_saml?(document, soft) &&
|
|
138
|
+
validate_response_state(soft) &&
|
|
139
|
+
validate_conditions(soft) &&
|
|
140
|
+
validate_issuer(soft) &&
|
|
141
|
+
document.validate_document(get_fingerprint, soft) &&
|
|
142
|
+
validate_success_status(soft)
|
|
191
143
|
end
|
|
192
144
|
|
|
193
|
-
# Validates that the SAML Response only contains a single Assertion (encrypted or not).
|
|
194
|
-
# @return [Boolean] True if the SAML Response contains one unique Assertion, otherwise False
|
|
195
|
-
#
|
|
196
|
-
def validate_num_assertion(soft = true)
|
|
197
|
-
assertions = REXML::XPath.match(
|
|
198
|
-
document,
|
|
199
|
-
"//a:Assertion",
|
|
200
|
-
{ "a" => ASSERTION }
|
|
201
|
-
)
|
|
202
|
-
encrypted_assertions = REXML::XPath.match(
|
|
203
|
-
document,
|
|
204
|
-
"//a:EncryptedAssertion",
|
|
205
|
-
{ "a" => ASSERTION }
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
unless assertions.size + encrypted_assertions.size == 1
|
|
209
|
-
return soft ? false : validation_error("SAML Response must contain 1 assertion")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
true
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Validates the Signed elements
|
|
216
|
-
# @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response
|
|
217
|
-
# an are a Response or an Assertion Element, otherwise False if soft=True
|
|
218
|
-
#
|
|
219
|
-
def validate_signed_elements(soft)
|
|
220
|
-
signature_nodes = REXML::XPath.match(
|
|
221
|
-
document,
|
|
222
|
-
"//ds:Signature",
|
|
223
|
-
{"ds"=>DSIG}
|
|
224
|
-
)
|
|
225
|
-
signed_elements = []
|
|
226
|
-
verified_seis = []
|
|
227
|
-
verified_ids = []
|
|
228
|
-
signature_nodes.each do |signature_node|
|
|
229
|
-
signed_element = signature_node.parent.name
|
|
230
|
-
if signed_element != 'Response' && signed_element != 'Assertion'
|
|
231
|
-
return soft ? false : validation_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
if signature_node.parent.attributes['ID'].nil?
|
|
235
|
-
return soft ? false : validation_error("Signed Element must contain an ID. SAML Response rejected")
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
id = signature_node.parent.attributes.get_attribute("ID").value
|
|
239
|
-
if verified_ids.include?(id)
|
|
240
|
-
return soft ? false : validation_error("Duplicated ID. SAML Response rejected")
|
|
241
|
-
end
|
|
242
|
-
verified_ids.push(id)
|
|
243
|
-
|
|
244
|
-
# Check that reference URI matches the parent ID and no duplicate References or IDs
|
|
245
|
-
ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
|
|
246
|
-
if ref
|
|
247
|
-
uri = ref.attributes.get_attribute("URI")
|
|
248
|
-
if uri && !uri.value.empty?
|
|
249
|
-
sei = uri.value[1..-1]
|
|
250
|
-
|
|
251
|
-
unless sei == id
|
|
252
|
-
return soft ? false : validation_error("Found an invalid Signed Element. SAML Response rejected")
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
if verified_seis.include?(sei)
|
|
256
|
-
return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
verified_seis.push(sei)
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
signed_elements << signed_element
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
unless signature_nodes.length < 3 && !signed_elements.empty?
|
|
267
|
-
return soft ? false : validation_error("Found an unexpected number of Signature Element. SAML Response rejected")
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
true
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# Validates the Status of the SAML Response
|
|
274
|
-
# @return [Boolean] True if the SAML Response contains a Success code, otherwise False if soft == false
|
|
275
|
-
# @raise [ValidationError] if soft == false and validation fails
|
|
276
|
-
#
|
|
277
145
|
def validate_success_status(soft = true)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
error_msg = 'The status code of the Response was not Success'
|
|
283
|
-
status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
|
|
284
|
-
return validation_error(status_error_msg)
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# Checks if the Status has the "Success" code
|
|
288
|
-
# @return [Boolean] True if the StatusCode is Sucess
|
|
289
|
-
#
|
|
290
|
-
def success?
|
|
291
|
-
status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
# @return [String] StatusCode value from a SAML Response.
|
|
295
|
-
#
|
|
296
|
-
def status_code
|
|
297
|
-
@status_code ||= begin
|
|
298
|
-
nodes = REXML::XPath.match(
|
|
299
|
-
document,
|
|
300
|
-
"/p:Response/p:Status/p:StatusCode",
|
|
301
|
-
{ "p" => PROTOCOL }
|
|
302
|
-
)
|
|
303
|
-
if nodes.size == 1
|
|
304
|
-
node = nodes[0]
|
|
305
|
-
code = node.attributes["Value"] if node && node.attributes
|
|
306
|
-
|
|
307
|
-
unless code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
308
|
-
nodes = REXML::XPath.match(
|
|
309
|
-
document,
|
|
310
|
-
"/p:Response/p:Status/p:StatusCode/p:StatusCode",
|
|
311
|
-
{ "p" => PROTOCOL }
|
|
312
|
-
)
|
|
313
|
-
statuses = nodes.collect do |inner_node|
|
|
314
|
-
inner_node.attributes["Value"]
|
|
315
|
-
end
|
|
316
|
-
extra_code = statuses.join(" | ")
|
|
317
|
-
if extra_code
|
|
318
|
-
code = "#{code} | #{extra_code}"
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
code
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# @return [String] the StatusMessage value from a SAML Response.
|
|
327
|
-
#
|
|
328
|
-
def status_message
|
|
329
|
-
@status_message ||= begin
|
|
330
|
-
nodes = REXML::XPath.match(
|
|
331
|
-
document,
|
|
332
|
-
"/p:Response/p:Status/p:StatusMessage",
|
|
333
|
-
{ "p" => PROTOCOL }
|
|
334
|
-
)
|
|
335
|
-
if nodes.size == 1
|
|
336
|
-
Utils.element_text(nodes.first)
|
|
337
|
-
end
|
|
146
|
+
if success?
|
|
147
|
+
true
|
|
148
|
+
else
|
|
149
|
+
soft ? false : validation_error(status_message)
|
|
338
150
|
end
|
|
339
151
|
end
|
|
340
152
|
|
|
341
153
|
def validate_structure(soft = true)
|
|
342
154
|
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
|
343
|
-
@schema = Nokogiri::XML::Schema(IO.read('
|
|
155
|
+
@schema = Nokogiri::XML::Schema(IO.read('saml-schema-protocol-2.0.xsd'))
|
|
344
156
|
@xml = Nokogiri::XML(self.document.to_s)
|
|
345
157
|
end
|
|
346
158
|
if soft
|
|
347
|
-
@schema.validate(@xml).map{
|
|
159
|
+
@schema.validate(@xml).map{
|
|
160
|
+
@errors << "Schema validation failed";
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
348
163
|
else
|
|
349
|
-
@schema.validate(@xml).map{ |error|
|
|
164
|
+
@schema.validate(@xml).map{ |error| @errors << "#{error.message}\n\n#{@xml.to_s}";
|
|
165
|
+
validation_error("#{error.message}\n\n#{@xml.to_s}")
|
|
166
|
+
}
|
|
350
167
|
end
|
|
351
168
|
end
|
|
352
169
|
|
|
@@ -382,24 +199,13 @@ module OneLogin
|
|
|
382
199
|
node
|
|
383
200
|
end
|
|
384
201
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
document,
|
|
393
|
-
"/p:Response/a:Assertion[@ID=$id]#{subelt}",
|
|
394
|
-
{ "p" => PROTOCOL, "a" => ASSERTION },
|
|
395
|
-
{ 'id' => document.signed_element_id }
|
|
396
|
-
)
|
|
397
|
-
node.concat( REXML::XPath.match(
|
|
398
|
-
document,
|
|
399
|
-
"/p:Response[@ID=$id]/a:Assertion#{subelt}",
|
|
400
|
-
{ "p" => PROTOCOL, "a" => ASSERTION },
|
|
401
|
-
{ 'id' => document.signed_element_id }
|
|
402
|
-
))
|
|
202
|
+
def get_fingerprint
|
|
203
|
+
if settings.idp_cert
|
|
204
|
+
cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
|
|
205
|
+
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
|
206
|
+
else
|
|
207
|
+
settings.idp_cert_fingerprint
|
|
208
|
+
end
|
|
403
209
|
end
|
|
404
210
|
|
|
405
211
|
def validate_conditions(soft = true)
|
|
@@ -409,10 +215,12 @@ module OneLogin
|
|
|
409
215
|
now = Time.now.utc
|
|
410
216
|
|
|
411
217
|
if not_before && (now + (options[:allowed_clock_drift] || 0)) < not_before
|
|
218
|
+
@errors << "Current time is earlier than NotBefore condition #{(now + (options[:allowed_clock_drift] || 0))} < #{not_before})"
|
|
412
219
|
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
|
413
220
|
end
|
|
414
221
|
|
|
415
222
|
if not_on_or_after && now >= not_on_or_after
|
|
223
|
+
@errors << "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after})"
|
|
416
224
|
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
|
417
225
|
end
|
|
418
226
|
|
|
@@ -422,64 +230,9 @@ module OneLogin
|
|
|
422
230
|
def validate_issuer(soft = true)
|
|
423
231
|
return true if settings.idp_entity_id.nil?
|
|
424
232
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
rescue ValidationError => e
|
|
428
|
-
return soft ? false : validation_error("Error while extracting issuers")
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
obtained_issuers.each do |issuer|
|
|
432
|
-
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
|
|
433
|
-
error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
|
|
434
|
-
return soft ? false : validation_error(error_msg)
|
|
435
|
-
end
|
|
233
|
+
unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
|
|
234
|
+
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>")
|
|
436
235
|
end
|
|
437
|
-
|
|
438
|
-
true
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
def validate_signature(soft = true)
|
|
442
|
-
error_msg = "Invalid Signature on SAML Response"
|
|
443
|
-
|
|
444
|
-
sig_elements = REXML::XPath.match(
|
|
445
|
-
document,
|
|
446
|
-
"/p:Response[@ID=$id]/ds:Signature]",
|
|
447
|
-
{ "p" => PROTOCOL, "ds" => DSIG },
|
|
448
|
-
{ 'id' => document.signed_element_id }
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
# Check signature nodes
|
|
452
|
-
if sig_elements.nil? || sig_elements.size == 0
|
|
453
|
-
sig_elements = REXML::XPath.match(
|
|
454
|
-
document,
|
|
455
|
-
"/p:Response/a:Assertion[@ID=$id]/ds:Signature",
|
|
456
|
-
{"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG},
|
|
457
|
-
{ 'id' => document.signed_element_id }
|
|
458
|
-
)
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
if sig_elements.size != 1
|
|
462
|
-
if sig_elements.size == 0
|
|
463
|
-
error_msg += ". Signed element id ##{doc.signed_element_id} is not found"
|
|
464
|
-
else
|
|
465
|
-
error_msg += ". Signed element id ##{doc.signed_element_id} is found more than once"
|
|
466
|
-
end
|
|
467
|
-
return soft ? false : validation_error(error_msg)
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
opts = {}
|
|
471
|
-
opts[:fingerprint_alg] = OpenSSL::Digest::SHA1.new
|
|
472
|
-
opts[:cert] = settings.get_idp_cert
|
|
473
|
-
fingerprint = settings.get_fingerprint
|
|
474
|
-
|
|
475
|
-
unless fingerprint
|
|
476
|
-
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
unless document.validate_document(fingerprint, soft, opts)
|
|
480
|
-
return soft ? false : validation_error(error_msg)
|
|
481
|
-
end
|
|
482
|
-
|
|
483
236
|
true
|
|
484
237
|
end
|
|
485
238
|
|
|
@@ -488,19 +241,6 @@ module OneLogin
|
|
|
488
241
|
Time.parse(node.attributes[attribute])
|
|
489
242
|
end
|
|
490
243
|
end
|
|
491
|
-
|
|
492
|
-
def validate_audience(soft = true)
|
|
493
|
-
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
|
494
|
-
|
|
495
|
-
unless audiences.include? settings.sp_entity_id
|
|
496
|
-
s = audiences.count > 1 ? 's' : '';
|
|
497
|
-
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
|
|
498
|
-
return soft ? false : validation_error(error_msg)
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
true
|
|
502
|
-
end
|
|
503
|
-
|
|
504
244
|
end
|
|
505
245
|
end
|
|
506
246
|
end
|