ruby-saml 0.8.10 → 0.8.11
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/lib/onelogin/ruby-saml/authrequest.rb +3 -2
- data/lib/onelogin/ruby-saml/logoutrequest.rb +3 -0
- data/lib/onelogin/ruby-saml/response.rb +143 -0
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +3 -2
- data/lib/onelogin/ruby-saml/utils.rb +25 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10ec0e5a4a9fc6a7f65613599597cef7ae8b8293
|
4
|
+
data.tar.gz: 0b63798d5d6b78e3073ccb899e355e6aa0134648
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1883986a5fd1b3925e6745bf7d259a907c656d36958efe50dfb760192c8273a3723b76344ee542a3a07b84da677808fad850b8bd272d949c5ce7fcd8c171a881
|
7
|
+
data.tar.gz: 8538b7c75961e99186b320ff1425fb82fa0c759e3e7267eaad76659adf9f0ba98249207122092dbc383f464c1c600b03a37f010d5a3e8bfa7fbb6fba0aaa8915
|
@@ -2,6 +2,7 @@ require "base64"
|
|
2
2
|
require "zlib"
|
3
3
|
require "cgi"
|
4
4
|
require "onelogin/ruby-saml/utils"
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
5
6
|
|
6
7
|
module OneLogin
|
7
8
|
module RubySaml
|
@@ -25,7 +26,7 @@ module OneLogin
|
|
25
26
|
params.each_pair do |key, value|
|
26
27
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
27
28
|
end
|
28
|
-
raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
|
29
|
+
raise SettingError.new "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty?
|
29
30
|
@login_url = settings.idp_sso_target_url + request_params
|
30
31
|
end
|
31
32
|
|
@@ -101,7 +102,7 @@ module OneLogin
|
|
101
102
|
root.attributes['ID'] = uuid
|
102
103
|
root.attributes['IssueInstant'] = time
|
103
104
|
root.attributes['Version'] = "2.0"
|
104
|
-
root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil?
|
105
|
+
root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty?
|
105
106
|
root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
|
106
107
|
root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
|
107
108
|
root.attributes['ForceAuthn'] = settings.force_authn unless settings.force_authn.nil?
|
@@ -3,6 +3,7 @@ require "zlib"
|
|
3
3
|
require "cgi"
|
4
4
|
require 'rexml/document'
|
5
5
|
require "onelogin/ruby-saml/utils"
|
6
|
+
require "onelogin/ruby-saml/setting_error"
|
6
7
|
|
7
8
|
module OneLogin
|
8
9
|
module RubySaml
|
@@ -23,6 +24,7 @@ module OneLogin
|
|
23
24
|
params.each_pair do |key, value|
|
24
25
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
25
26
|
end
|
27
|
+
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?
|
26
28
|
@logout_url = settings.idp_slo_target_url + request_params
|
27
29
|
end
|
28
30
|
|
@@ -103,6 +105,7 @@ module OneLogin
|
|
103
105
|
root.attributes['ID'] = uuid
|
104
106
|
root.attributes['IssueInstant'] = time
|
105
107
|
root.attributes['Version'] = "2.0"
|
108
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
106
109
|
|
107
110
|
if settings.sp_entity_id
|
108
111
|
issuer = root.add_element "saml:Issuer", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
@@ -42,6 +42,8 @@ module OneLogin
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
alias nameid name_id
|
46
|
+
|
45
47
|
def sessionindex
|
46
48
|
@sessionindex ||= begin
|
47
49
|
node = xpath_first_from_signed_assertion('/a:AuthnStatement')
|
@@ -147,6 +149,9 @@ module OneLogin
|
|
147
149
|
end
|
148
150
|
|
149
151
|
def validate(soft = true)
|
152
|
+
validate_success_status &&
|
153
|
+
validate_num_assertion &&
|
154
|
+
validate_signed_elements &&
|
150
155
|
validate_structure(soft) &&
|
151
156
|
validate_response_state(soft) &&
|
152
157
|
validate_conditions(soft) &&
|
@@ -155,6 +160,144 @@ module OneLogin
|
|
155
160
|
success?
|
156
161
|
end
|
157
162
|
|
163
|
+
# Validates that the SAML Response only contains a single Assertion (encrypted or not).
|
164
|
+
# @return [Boolean] True if the SAML Response contains one unique Assertion, otherwise False
|
165
|
+
#
|
166
|
+
def validate_num_assertion(soft = true)
|
167
|
+
assertions = REXML::XPath.match(
|
168
|
+
document,
|
169
|
+
"//a:Assertion",
|
170
|
+
{ "a" => ASSERTION }
|
171
|
+
)
|
172
|
+
encrypted_assertions = REXML::XPath.match(
|
173
|
+
document,
|
174
|
+
"//a:EncryptedAssertion",
|
175
|
+
{ "a" => ASSERTION }
|
176
|
+
)
|
177
|
+
|
178
|
+
unless assertions.size != 0
|
179
|
+
return soft ? false : validation_error("Encrypted assertion is not supported")
|
180
|
+
end
|
181
|
+
|
182
|
+
unless assertions.size + encrypted_assertions.size == 1
|
183
|
+
return soft ? false : validation_error("SAML Response must contain 1 assertion")
|
184
|
+
end
|
185
|
+
|
186
|
+
true
|
187
|
+
end
|
188
|
+
|
189
|
+
# Validates the Signed elements
|
190
|
+
# @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response
|
191
|
+
# an are a Response or an Assertion Element, otherwise False if soft=True
|
192
|
+
#
|
193
|
+
def validate_signed_elements
|
194
|
+
signature_nodes = REXML::XPath.match(
|
195
|
+
document,
|
196
|
+
"//ds:Signature",
|
197
|
+
{"ds"=>DSIG}
|
198
|
+
)
|
199
|
+
signed_elements = []
|
200
|
+
verified_seis = []
|
201
|
+
verified_ids = []
|
202
|
+
signature_nodes.each do |signature_node|
|
203
|
+
signed_element = signature_node.parent.name
|
204
|
+
if signed_element != 'Response' && signed_element != 'Assertion'
|
205
|
+
return soft ? false : validation_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
|
206
|
+
end
|
207
|
+
|
208
|
+
if signature_node.parent.attributes['ID'].nil?
|
209
|
+
return soft ? false : validation_error("Signed Element must contain an ID. SAML Response rejected")
|
210
|
+
end
|
211
|
+
|
212
|
+
id = signature_node.parent.attributes.get_attribute("ID").value
|
213
|
+
if verified_ids.include?(id)
|
214
|
+
return soft ? false : validation_error("Duplicated ID. SAML Response rejected")
|
215
|
+
end
|
216
|
+
verified_ids.push(id)
|
217
|
+
|
218
|
+
# Check that reference URI matches the parent ID and no duplicate References or IDs
|
219
|
+
ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
|
220
|
+
if ref
|
221
|
+
uri = ref.attributes.get_attribute("URI")
|
222
|
+
if uri && !uri.value.empty?
|
223
|
+
sei = uri.value[1..-1]
|
224
|
+
|
225
|
+
unless sei == id
|
226
|
+
return soft ? false : validation_error("Found an invalid Signed Element. SAML Response rejected")
|
227
|
+
end
|
228
|
+
|
229
|
+
if verified_seis.include?(sei)
|
230
|
+
return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
|
231
|
+
return append_error("Duplicated Reference URI. SAML Response rejected")
|
232
|
+
end
|
233
|
+
|
234
|
+
verified_seis.push(sei)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
signed_elements << signed_element
|
239
|
+
end
|
240
|
+
|
241
|
+
unless signature_nodes.length < 3 && !signed_elements.empty?
|
242
|
+
return soft ? false : validation_error("Found an unexpected number of Signature Element. SAML Response rejected")
|
243
|
+
end
|
244
|
+
|
245
|
+
true
|
246
|
+
end
|
247
|
+
|
248
|
+
# Validates the Status of the SAML Response
|
249
|
+
# @return [Boolean] True if the SAML Response contains a Success code, otherwise False if soft == false
|
250
|
+
# @raise [ValidationError] if soft == false and validation fails
|
251
|
+
#
|
252
|
+
def validate_success_status
|
253
|
+
return true if success?
|
254
|
+
|
255
|
+
return false unless soft
|
256
|
+
|
257
|
+
error_msg = 'The status code of the Response was not Success'
|
258
|
+
status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
|
259
|
+
return validation_error(status_error_msg)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Checks if the Status has the "Success" code
|
263
|
+
# @return [Boolean] True if the StatusCode is Sucess
|
264
|
+
#
|
265
|
+
def success?
|
266
|
+
status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [String] StatusCode value from a SAML Response.
|
270
|
+
#
|
271
|
+
def status_code
|
272
|
+
@status_code ||= begin
|
273
|
+
nodes = REXML::XPath.match(
|
274
|
+
document,
|
275
|
+
"/p:Response/p:Status/p:StatusCode",
|
276
|
+
{ "p" => PROTOCOL }
|
277
|
+
)
|
278
|
+
if nodes.size == 1
|
279
|
+
node = nodes[0]
|
280
|
+
code = node.attributes["Value"] if node && node.attributes
|
281
|
+
|
282
|
+
unless code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
283
|
+
nodes = REXML::XPath.match(
|
284
|
+
document,
|
285
|
+
"/p:Response/p:Status/p:StatusCode/p:StatusCode",
|
286
|
+
{ "p" => PROTOCOL }
|
287
|
+
)
|
288
|
+
statuses = nodes.collect do |inner_node|
|
289
|
+
inner_node.attributes["Value"]
|
290
|
+
end
|
291
|
+
extra_code = statuses.join(" | ")
|
292
|
+
if extra_code
|
293
|
+
code = "#{code} | #{extra_code}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
code
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
158
301
|
def validate_structure(soft = true)
|
159
302
|
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
160
303
|
@schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
|
@@ -2,6 +2,7 @@ require "base64"
|
|
2
2
|
require "zlib"
|
3
3
|
require "cgi"
|
4
4
|
require "onelogin/ruby-saml/utils"
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
5
6
|
|
6
7
|
module OneLogin
|
7
8
|
module RubySaml
|
@@ -35,7 +36,7 @@ module OneLogin
|
|
35
36
|
params.each_pair do |key, value|
|
36
37
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
37
38
|
end
|
38
|
-
|
39
|
+
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?
|
39
40
|
@logout_url = settings.idp_slo_target_url + response_params
|
40
41
|
end
|
41
42
|
|
@@ -119,7 +120,7 @@ module OneLogin
|
|
119
120
|
root.attributes['IssueInstant'] = time
|
120
121
|
root.attributes['Version'] = '2.0'
|
121
122
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
122
|
-
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
123
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
123
124
|
|
124
125
|
if settings.sp_entity_id != nil
|
125
126
|
issuer = root.add_element "saml:Issuer"
|
@@ -89,6 +89,31 @@ module OneLogin
|
|
89
89
|
def self.uuid
|
90
90
|
RUBY_VERSION < '1.9' ? "_#{@@uuid_generator.generate}" : "_#{SecureRandom.uuid}"
|
91
91
|
end
|
92
|
+
|
93
|
+
# Build the status error message
|
94
|
+
# @param status_code [String] StatusCode value
|
95
|
+
# @param status_message [Strig] StatusMessage value
|
96
|
+
# @return [String] The status error message
|
97
|
+
def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil)
|
98
|
+
unless raw_status_code.nil?
|
99
|
+
if raw_status_code.include? "|"
|
100
|
+
status_codes = raw_status_code.split(' | ')
|
101
|
+
values = status_codes.collect do |status_code|
|
102
|
+
status_code.split(':').last
|
103
|
+
end
|
104
|
+
printable_code = values.join(" => ")
|
105
|
+
else
|
106
|
+
printable_code = raw_status_code.split(':').last
|
107
|
+
end
|
108
|
+
error_msg << ', was ' + printable_code
|
109
|
+
end
|
110
|
+
|
111
|
+
unless status_message.nil?
|
112
|
+
error_msg << ' -> ' + status_message
|
113
|
+
end
|
114
|
+
|
115
|
+
error_msg
|
116
|
+
end
|
92
117
|
end
|
93
118
|
end
|
94
119
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OneLogin LLC
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uuid
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- lib/onelogin/ruby-saml/logoutresponse.rb
|
62
62
|
- lib/onelogin/ruby-saml/metadata.rb
|
63
63
|
- lib/onelogin/ruby-saml/response.rb
|
64
|
+
- lib/onelogin/ruby-saml/setting_error.rb
|
64
65
|
- lib/onelogin/ruby-saml/settings.rb
|
65
66
|
- lib/onelogin/ruby-saml/slo_logoutresponse.rb
|
66
67
|
- lib/onelogin/ruby-saml/utils.rb
|
@@ -127,7 +128,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
128
|
- !ruby/object:Gem::Version
|
128
129
|
version: '0'
|
129
130
|
requirements: []
|
130
|
-
|
131
|
+
rubyforge_project: http://www.rubygems.org/gems/ruby-saml
|
132
|
+
rubygems_version: 2.5.1
|
131
133
|
signing_key:
|
132
134
|
specification_version: 4
|
133
135
|
summary: SAML Ruby Tookit
|