ruby-saml 0.8.10 → 0.8.11
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 +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
|