ruby-saml 1.12.4 → 1.18.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 +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +29 -2
- data/{changelog.md → CHANGELOG.md} +64 -15
- data/LICENSE +2 -1
- data/README.md +425 -233
- data/UPGRADING.md +158 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +9 -11
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +115 -84
- data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -9
- data/lib/onelogin/ruby-saml/logoutresponse.rb +2 -2
- data/lib/onelogin/ruby-saml/metadata.rb +75 -42
- data/lib/onelogin/ruby-saml/response.rb +130 -70
- data/lib/onelogin/ruby-saml/saml_message.rb +16 -19
- data/lib/onelogin/ruby-saml/settings.rb +214 -110
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +51 -37
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +9 -9
- data/lib/onelogin/ruby-saml/utils.rb +129 -46
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +81 -48
- data/ruby-saml.gemspec +40 -14
- metadata +29 -32
- data/.travis.yml +0 -48
|
@@ -43,7 +43,7 @@ module OneLogin
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
@request = decode_raw_saml(request)
|
|
46
|
+
@request = decode_raw_saml(request, settings)
|
|
47
47
|
@document = REXML::Document.new(@request)
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -62,10 +62,7 @@ module OneLogin
|
|
|
62
62
|
# @return [String] Gets the NameID of the Logout Request.
|
|
63
63
|
#
|
|
64
64
|
def name_id
|
|
65
|
-
@name_id ||=
|
|
66
|
-
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
67
|
-
Utils.element_text(node)
|
|
68
|
-
end
|
|
65
|
+
@name_id ||= Utils.element_text(name_id_node)
|
|
69
66
|
end
|
|
70
67
|
|
|
71
68
|
alias_method :nameid, :name_id
|
|
@@ -73,15 +70,49 @@ module OneLogin
|
|
|
73
70
|
# @return [String] Gets the NameID Format of the Logout Request.
|
|
74
71
|
#
|
|
75
72
|
def name_id_format
|
|
76
|
-
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
77
73
|
@name_id_format ||=
|
|
78
|
-
if
|
|
79
|
-
|
|
74
|
+
if name_id_node && name_id_node.attribute("Format")
|
|
75
|
+
name_id_node.attribute("Format").value
|
|
80
76
|
end
|
|
81
77
|
end
|
|
82
78
|
|
|
83
79
|
alias_method :nameid_format, :name_id_format
|
|
84
80
|
|
|
81
|
+
def name_id_node
|
|
82
|
+
@name_id_node ||=
|
|
83
|
+
begin
|
|
84
|
+
encrypted_node = REXML::XPath.first(document, "/p:LogoutRequest/a:EncryptedID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
85
|
+
if encrypted_node
|
|
86
|
+
node = decrypt_nameid(encrypted_node)
|
|
87
|
+
else
|
|
88
|
+
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Decrypts an EncryptedID element
|
|
94
|
+
# @param encrypted_id_node [REXML::Element] The EncryptedID element
|
|
95
|
+
# @return [REXML::Document] The decrypted EncrypedtID element
|
|
96
|
+
#
|
|
97
|
+
def decrypt_nameid(encrypted_id_node)
|
|
98
|
+
|
|
99
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
|
100
|
+
raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys)
|
|
104
|
+
# If we get some problematic noise in the plaintext after decrypting.
|
|
105
|
+
# This quick regexp parse will grab only the Element and discard the noise.
|
|
106
|
+
elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]
|
|
107
|
+
|
|
108
|
+
# To avoid namespace errors if saml namespace is not defined
|
|
109
|
+
# create a parent node first with the namespace defined
|
|
110
|
+
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
|
|
111
|
+
elem_plaintext = node_header + elem_plaintext + '</node>'
|
|
112
|
+
doc = REXML::Document.new(elem_plaintext)
|
|
113
|
+
doc.root[0]
|
|
114
|
+
end
|
|
115
|
+
|
|
85
116
|
# @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
|
|
86
117
|
#
|
|
87
118
|
def id
|
|
@@ -130,6 +161,12 @@ module OneLogin
|
|
|
130
161
|
|
|
131
162
|
private
|
|
132
163
|
|
|
164
|
+
# returns the allowed clock drift on timing validation
|
|
165
|
+
# @return [Float]
|
|
166
|
+
def allowed_clock_drift
|
|
167
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
|
168
|
+
end
|
|
169
|
+
|
|
133
170
|
# Hard aux function to validate the Logout Request
|
|
134
171
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
|
135
172
|
# @return [Boolean] TRUE if the Logout Request is valid
|
|
@@ -180,15 +217,17 @@ module OneLogin
|
|
|
180
217
|
true
|
|
181
218
|
end
|
|
182
219
|
|
|
183
|
-
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
|
220
|
+
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
|
221
|
+
# option, the timing validations are relaxed by the allowed_clock_drift value)
|
|
184
222
|
# If fails, the error is added to the errors array
|
|
185
223
|
# @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
|
|
186
224
|
# @raise [ValidationError] if soft == false and validation fails
|
|
187
225
|
#
|
|
188
226
|
def validate_not_on_or_after
|
|
189
227
|
now = Time.now.utc
|
|
190
|
-
|
|
191
|
-
|
|
228
|
+
|
|
229
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
|
230
|
+
return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})")
|
|
192
231
|
end
|
|
193
232
|
|
|
194
233
|
true
|
|
@@ -241,32 +280,8 @@ module OneLogin
|
|
|
241
280
|
return true unless options.has_key? :get_params
|
|
242
281
|
return true unless options[:get_params].has_key? 'Signature'
|
|
243
282
|
|
|
244
|
-
|
|
245
|
-
# of URI-encoded values _as sent by the IDP_:
|
|
246
|
-
#
|
|
247
|
-
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
|
|
248
|
-
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
|
|
249
|
-
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
|
|
250
|
-
# > processed by software because the resulting encoding may not match the signer's encoding.
|
|
251
|
-
#
|
|
252
|
-
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
|
|
253
|
-
#
|
|
254
|
-
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
|
|
255
|
-
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
|
|
256
|
-
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
|
|
257
|
-
options[:raw_get_params] ||= {}
|
|
258
|
-
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
|
|
259
|
-
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
|
|
260
|
-
end
|
|
261
|
-
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
|
|
262
|
-
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
|
|
263
|
-
end
|
|
264
|
-
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
|
|
265
|
-
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
|
|
266
|
-
end
|
|
283
|
+
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
|
|
267
284
|
|
|
268
|
-
# If we only received the raw version of SigAlg,
|
|
269
|
-
# then parse it back into the decoded params hash for convenience.
|
|
270
285
|
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
|
|
271
286
|
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
|
|
272
287
|
end
|
|
@@ -328,7 +343,6 @@ module OneLogin
|
|
|
328
343
|
|
|
329
344
|
true
|
|
330
345
|
end
|
|
331
|
-
|
|
332
346
|
end
|
|
333
347
|
end
|
|
334
348
|
end
|
|
@@ -13,7 +13,7 @@ module OneLogin
|
|
|
13
13
|
class SloLogoutresponse < SamlMessage
|
|
14
14
|
|
|
15
15
|
# Logout Response ID
|
|
16
|
-
|
|
16
|
+
attr_accessor :uuid
|
|
17
17
|
|
|
18
18
|
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
|
19
19
|
# Asigns an ID, a random uuid.
|
|
@@ -41,7 +41,7 @@ module OneLogin
|
|
|
41
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
|
42
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
|
43
43
|
params.each_pair do |key, value|
|
|
44
|
-
response_params << "&#{key
|
|
44
|
+
response_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
|
@@ -70,7 +70,7 @@ module OneLogin
|
|
|
70
70
|
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
|
71
71
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
72
72
|
|
|
73
|
-
response = ""
|
|
73
|
+
response = "".dup
|
|
74
74
|
response_doc.write(response)
|
|
75
75
|
|
|
76
76
|
Logging.debug "Created SLO Logout Response: #{response}"
|
|
@@ -78,9 +78,10 @@ module OneLogin
|
|
|
78
78
|
response = deflate(response) if settings.compress_response
|
|
79
79
|
base64_response = encode(response)
|
|
80
80
|
response_params = {"SAMLResponse" => base64_response}
|
|
81
|
+
sp_signing_key = settings.get_sp_signing_key
|
|
81
82
|
|
|
82
|
-
if settings.
|
|
83
|
-
params['SigAlg']
|
|
83
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key
|
|
84
|
+
params['SigAlg'] = settings.security[:signature_method]
|
|
84
85
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
85
86
|
:type => 'SAMLResponse',
|
|
86
87
|
:data => base64_response,
|
|
@@ -88,7 +89,7 @@ module OneLogin
|
|
|
88
89
|
:sig_alg => params['SigAlg']
|
|
89
90
|
)
|
|
90
91
|
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
91
|
-
signature =
|
|
92
|
+
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
|
|
92
93
|
params['Signature'] = encode(signature)
|
|
93
94
|
end
|
|
94
95
|
|
|
@@ -150,9 +151,8 @@ module OneLogin
|
|
|
150
151
|
|
|
151
152
|
def sign_document(document, settings)
|
|
152
153
|
# embed signature
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
cert = settings.get_sp_cert
|
|
154
|
+
cert, private_key = settings.get_sp_signing_pair
|
|
155
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert
|
|
156
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
157
157
|
end
|
|
158
158
|
|
|
@@ -13,20 +13,47 @@ module OneLogin
|
|
|
13
13
|
class Utils
|
|
14
14
|
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
|
17
|
+
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
|
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
|
19
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
|
|
20
|
+
DURATION_FORMAT = %r(^
|
|
21
|
+
(-?)P # 1: Duration sign
|
|
22
|
+
(?:
|
|
23
|
+
(?:(\d+)Y)? # 2: Years
|
|
24
|
+
(?:(\d+)M)? # 3: Months
|
|
25
|
+
(?:(\d+)D)? # 4: Days
|
|
26
|
+
(?:T
|
|
27
|
+
(?:(\d+)H)? # 5: Hours
|
|
28
|
+
(?:(\d+)M)? # 6: Minutes
|
|
29
|
+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
|
|
30
|
+
)?
|
|
31
|
+
|
|
|
32
|
+
(\d+)W # 8: Weeks
|
|
33
|
+
)
|
|
34
|
+
$)x.freeze
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
UUID_PREFIX = '_'
|
|
37
|
+
@@prefix = '_'
|
|
38
|
+
|
|
39
|
+
# Checks if the x509 cert provided is expired.
|
|
23
40
|
#
|
|
41
|
+
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
|
|
42
|
+
# @return [true|false] Whether the certificate is expired.
|
|
24
43
|
def self.is_cert_expired(cert)
|
|
25
|
-
if cert.is_a?(String)
|
|
26
|
-
cert = OpenSSL::X509::Certificate.new(cert)
|
|
27
|
-
end
|
|
44
|
+
cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String)
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
cert.not_after < Time.now
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Checks if the x509 cert provided has both started and has not expired.
|
|
50
|
+
#
|
|
51
|
+
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
|
|
52
|
+
# @return [true|false] Whether the certificate is currently active.
|
|
53
|
+
def self.is_cert_active(cert)
|
|
54
|
+
cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String)
|
|
55
|
+
now = Time.now
|
|
56
|
+
cert.not_before <= now && cert.not_after >= now
|
|
30
57
|
end
|
|
31
58
|
|
|
32
59
|
# Interprets a ISO8601 duration value relative to a given timestamp.
|
|
@@ -37,38 +64,33 @@ module OneLogin
|
|
|
37
64
|
# current time.
|
|
38
65
|
#
|
|
39
66
|
# @return [Integer] The new timestamp, after the duration is applied.
|
|
40
|
-
#
|
|
67
|
+
#
|
|
41
68
|
def self.parse_duration(duration, timestamp=Time.now.utc)
|
|
69
|
+
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
|
|
70
|
+
|
|
42
71
|
matches = duration.match(DURATION_FORMAT)
|
|
43
|
-
|
|
72
|
+
|
|
44
73
|
if matches.nil?
|
|
45
|
-
raise
|
|
74
|
+
raise StandardError.new("Invalid ISO 8601 duration")
|
|
46
75
|
end
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
durMonths = matches[3].to_i
|
|
50
|
-
durDays = matches[4].to_i
|
|
51
|
-
durHours = matches[5].to_i
|
|
52
|
-
durMinutes = matches[6].to_i
|
|
53
|
-
durSeconds = matches[7].to_f
|
|
54
|
-
durWeeks = matches[8].to_i
|
|
55
|
-
|
|
56
|
-
if matches[1] == "-"
|
|
57
|
-
durYears = -durYears
|
|
58
|
-
durMonths = -durMonths
|
|
59
|
-
durDays = -durDays
|
|
60
|
-
durHours = -durHours
|
|
61
|
-
durMinutes = -durMinutes
|
|
62
|
-
durSeconds = -durSeconds
|
|
63
|
-
durWeeks = -durWeeks
|
|
64
|
-
end
|
|
77
|
+
sign = matches[1] == '-' ? -1 : 1
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
|
80
|
+
matches[2..8].map do |match|
|
|
81
|
+
if match
|
|
82
|
+
match = match.tr(',', '.').gsub(/\.0*\z/, '')
|
|
83
|
+
sign * (match.include?('.') ? match.to_f : match.to_i)
|
|
84
|
+
else
|
|
85
|
+
0
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
datetime = Time.at(timestamp).utc.to_datetime
|
|
90
|
+
datetime = datetime.next_year(durYears)
|
|
91
|
+
datetime = datetime.next_month(durMonths)
|
|
92
|
+
datetime = datetime.next_day((7*durWeeks) + durDays)
|
|
93
|
+
datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
|
72
94
|
end
|
|
73
95
|
|
|
74
96
|
# Return a properly formatted x509 certificate
|
|
@@ -122,6 +144,28 @@ module OneLogin
|
|
|
122
144
|
"-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
|
|
123
145
|
end
|
|
124
146
|
|
|
147
|
+
# Given a certificate string, return an OpenSSL::X509::Certificate object.
|
|
148
|
+
#
|
|
149
|
+
# @param cert [String] The original certificate
|
|
150
|
+
# @return [OpenSSL::X509::Certificate] The certificate object
|
|
151
|
+
#
|
|
152
|
+
def self.build_cert_object(cert)
|
|
153
|
+
return nil if cert.nil? || cert.empty?
|
|
154
|
+
|
|
155
|
+
OpenSSL::X509::Certificate.new(format_cert(cert))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Given a private key string, return an OpenSSL::PKey::RSA object.
|
|
159
|
+
#
|
|
160
|
+
# @param cert [String] The original private key
|
|
161
|
+
# @return [OpenSSL::PKey::RSA] The private key object
|
|
162
|
+
#
|
|
163
|
+
def self.build_private_key_object(private_key)
|
|
164
|
+
return nil if private_key.nil? || private_key.empty?
|
|
165
|
+
|
|
166
|
+
OpenSSL::PKey::RSA.new(format_private_key(private_key))
|
|
167
|
+
end
|
|
168
|
+
|
|
125
169
|
# Build the Query String signature that will be used in the HTTP-Redirect binding
|
|
126
170
|
# to generate the Signature
|
|
127
171
|
# @param params [Hash] Parameters to build the Query String
|
|
@@ -161,30 +205,39 @@ module OneLogin
|
|
|
161
205
|
#
|
|
162
206
|
# @param rawparams [Hash] Raw GET Parameters
|
|
163
207
|
# @param params [Hash] GET Parameters
|
|
208
|
+
# @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatiblity)
|
|
164
209
|
# @return [Hash] New raw parameters
|
|
165
210
|
#
|
|
166
|
-
def self.prepare_raw_get_params(rawparams, params)
|
|
211
|
+
def self.prepare_raw_get_params(rawparams, params, lowercase_url_encoding=false)
|
|
167
212
|
rawparams ||= {}
|
|
168
213
|
|
|
169
214
|
if rawparams['SAMLRequest'].nil? && !params['SAMLRequest'].nil?
|
|
170
|
-
rawparams['SAMLRequest'] =
|
|
215
|
+
rawparams['SAMLRequest'] = escape_request_param(params['SAMLRequest'], lowercase_url_encoding)
|
|
171
216
|
end
|
|
172
217
|
if rawparams['SAMLResponse'].nil? && !params['SAMLResponse'].nil?
|
|
173
|
-
rawparams['SAMLResponse'] =
|
|
218
|
+
rawparams['SAMLResponse'] = escape_request_param(params['SAMLResponse'], lowercase_url_encoding)
|
|
174
219
|
end
|
|
175
220
|
if rawparams['RelayState'].nil? && !params['RelayState'].nil?
|
|
176
|
-
rawparams['RelayState'] =
|
|
221
|
+
rawparams['RelayState'] = escape_request_param(params['RelayState'], lowercase_url_encoding)
|
|
177
222
|
end
|
|
178
223
|
if rawparams['SigAlg'].nil? && !params['SigAlg'].nil?
|
|
179
|
-
rawparams['SigAlg'] =
|
|
224
|
+
rawparams['SigAlg'] = escape_request_param(params['SigAlg'], lowercase_url_encoding)
|
|
180
225
|
end
|
|
181
226
|
|
|
182
227
|
rawparams
|
|
183
228
|
end
|
|
184
229
|
|
|
230
|
+
def self.escape_request_param(param, lowercase_url_encoding)
|
|
231
|
+
CGI.escape(param).tap do |escaped|
|
|
232
|
+
next unless lowercase_url_encoding
|
|
233
|
+
|
|
234
|
+
escaped.gsub!(/%[A-Fa-f0-9]{2}/) { |match| match.downcase }
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
185
238
|
# Validate the Signature parameter sent on the HTTP-Redirect binding
|
|
186
239
|
# @param params [Hash] Parameters to be used in the validation process
|
|
187
|
-
# @option params [OpenSSL::X509::Certificate] cert The
|
|
240
|
+
# @option params [OpenSSL::X509::Certificate] cert The IDP public certificate
|
|
188
241
|
# @option params [String] sig_alg The SigAlg parameter
|
|
189
242
|
# @option params [String] signature The Signature parameter (base64 encoded)
|
|
190
243
|
# @option params [String] query_string The full GET Query String to be compared
|
|
@@ -201,6 +254,8 @@ module OneLogin
|
|
|
201
254
|
# @param status_message [Strig] StatusMessage value
|
|
202
255
|
# @return [String] The status error message
|
|
203
256
|
def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil)
|
|
257
|
+
error_msg = error_msg.dup
|
|
258
|
+
|
|
204
259
|
unless raw_status_code.nil?
|
|
205
260
|
if raw_status_code.include? "|"
|
|
206
261
|
status_codes = raw_status_code.split(' | ')
|
|
@@ -221,9 +276,29 @@ module OneLogin
|
|
|
221
276
|
error_msg
|
|
222
277
|
end
|
|
223
278
|
|
|
279
|
+
# Obtains the decrypted string from an Encrypted node element in XML,
|
|
280
|
+
# given multiple private keys to try.
|
|
281
|
+
# @param encrypted_node [REXML::Element] The Encrypted element
|
|
282
|
+
# @param private_keys [Array<OpenSSL::PKey::RSA>] The Service provider private key
|
|
283
|
+
# @return [String] The decrypted data
|
|
284
|
+
def self.decrypt_multi(encrypted_node, private_keys)
|
|
285
|
+
raise ArgumentError.new('private_keys must be specified') if !private_keys || private_keys.empty?
|
|
286
|
+
|
|
287
|
+
error = nil
|
|
288
|
+
private_keys.each do |key|
|
|
289
|
+
begin
|
|
290
|
+
return decrypt_data(encrypted_node, key)
|
|
291
|
+
rescue OpenSSL::PKey::PKeyError => e
|
|
292
|
+
error ||= e
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
raise(error) if error
|
|
297
|
+
end
|
|
298
|
+
|
|
224
299
|
# Obtains the decrypted string from an Encrypted node element in XML
|
|
225
|
-
# @param encrypted_node [REXML::Element]
|
|
226
|
-
# @param private_key
|
|
300
|
+
# @param encrypted_node [REXML::Element] The Encrypted element
|
|
301
|
+
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
|
|
227
302
|
# @return [String] The decrypted data
|
|
228
303
|
def self.decrypt_data(encrypted_node, private_key)
|
|
229
304
|
encrypt_data = REXML::XPath.first(
|
|
@@ -287,7 +362,7 @@ module OneLogin
|
|
|
287
362
|
|
|
288
363
|
# Obtains the deciphered text
|
|
289
364
|
# @param cipher_text [String] The ciphered text
|
|
290
|
-
# @param symmetric_key [String] The
|
|
365
|
+
# @param symmetric_key [String] The symmetric key used to encrypt the text
|
|
291
366
|
# @param algorithm [String] The encrypted algorithm
|
|
292
367
|
# @return [String] The deciphered text
|
|
293
368
|
def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
|
|
@@ -328,8 +403,16 @@ module OneLogin
|
|
|
328
403
|
end
|
|
329
404
|
end
|
|
330
405
|
|
|
406
|
+
def self.set_prefix(value)
|
|
407
|
+
@@prefix = value
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def self.prefix
|
|
411
|
+
@@prefix
|
|
412
|
+
end
|
|
413
|
+
|
|
331
414
|
def self.uuid
|
|
332
|
-
RUBY_VERSION < '1.9' ? "
|
|
415
|
+
"#{prefix}" + (RUBY_VERSION < '1.9' ? "#{@@uuid_generator.generate}" : "#{SecureRandom.uuid}")
|
|
333
416
|
end
|
|
334
417
|
|
|
335
418
|
# Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
|