ruby-saml 1.10.2 → 1.11.0
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 +4 -4
- data/.travis.yml +13 -14
- data/README.md +20 -5
- data/lib/onelogin/ruby-saml/authrequest.rb +2 -2
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +2 -2
- data/lib/onelogin/ruby-saml/logoutresponse.rb +17 -2
- data/lib/onelogin/ruby-saml/metadata.rb +2 -2
- data/lib/onelogin/ruby-saml/response.rb +26 -7
- data/lib/onelogin/ruby-saml/settings.rb +37 -5
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +16 -1
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +2 -2
- data/lib/onelogin/ruby-saml/utils.rb +13 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/ruby-saml.gemspec +1 -1
- data/test/logoutresponse_test.rb +25 -7
- data/test/metadata_test.rb +1 -1
- data/test/response_test.rb +26 -17
- data/test/settings_test.rb +8 -0
- data/test/slo_logoutrequest_test.rb +19 -0
- data/test/test_helper.rb +2 -0
- data/test/xml_security_test.rb +1 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afcd8a95f66ec94e3bb68b5a5264a651ae623923
|
4
|
+
data.tar.gz: eb05bc60959fde11ac20b0af0d14c07b7c5c1b38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e85dc90f8f4bd5433f0078a6b5e316109dc7bbbe1ba60d5f8075d1f5047a71c208144d1da51157cf7220f2c56fb8b421e7b70ac5c828bea713146fc6bc774a29
|
7
|
+
data.tar.gz: 7cce3fd10ff5d7753d518b847b7f0ff0976b4ef5a1b7cc39c6e6cd3c8b32df7649e95ab929631b9838df5a4fb3eb3f3c50479f54253094b457a484710381b8f1
|
data/.travis.yml
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
-
sudo: false
|
2
1
|
language: ruby
|
3
2
|
rvm:
|
4
3
|
- 1.8.7
|
5
4
|
- 1.9.3
|
6
5
|
- 2.0.0
|
7
|
-
- 2.1.
|
8
|
-
- 2.2.
|
9
|
-
- 2.3.
|
10
|
-
- 2.4.
|
11
|
-
- 2.5.
|
12
|
-
- 2.6.
|
6
|
+
- 2.1.10
|
7
|
+
- 2.2.10
|
8
|
+
- 2.3.8
|
9
|
+
- 2.4.6
|
10
|
+
- 2.5.5
|
11
|
+
- 2.6.3
|
13
12
|
- ree
|
14
13
|
- jruby-1.7.27
|
15
14
|
- jruby-9.1.17.0
|
16
|
-
- jruby-9.2.
|
15
|
+
- jruby-9.2.7.0
|
17
16
|
gemfile:
|
18
17
|
- Gemfile
|
19
18
|
- gemfiles/nokogiri-1.5.gemfile
|
@@ -29,19 +28,19 @@ matrix:
|
|
29
28
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
30
29
|
- rvm: jruby-9.1.17.0
|
31
30
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
32
|
-
- rvm: jruby-9.2.
|
31
|
+
- rvm: jruby-9.2.7.0
|
33
32
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
34
33
|
- rvm: 2.1.5
|
35
34
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
36
|
-
- rvm: 2.2.
|
35
|
+
- rvm: 2.2.10
|
37
36
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
38
|
-
- rvm: 2.3.
|
37
|
+
- rvm: 2.3.8
|
39
38
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
40
|
-
- rvm: 2.4.
|
39
|
+
- rvm: 2.4.6
|
41
40
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
42
|
-
- rvm: 2.5.
|
41
|
+
- rvm: 2.5.5
|
43
42
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
44
|
-
- rvm: 2.6.
|
43
|
+
- rvm: 2.6.3
|
45
44
|
gemfile: gemfiles/nokogiri-1.5.gemfile
|
46
45
|
env:
|
47
46
|
- JRUBY_OPTS="--debug"
|
data/README.md
CHANGED
@@ -123,6 +123,17 @@ We created a demo project for Rails4 that uses the latest version of this librar
|
|
123
123
|
|
124
124
|
If you believe you have discovered a security vulnerability in this gem, please report it at https://www.onelogin.com/security with a description. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution.
|
125
125
|
|
126
|
+
### Security warning
|
127
|
+
|
128
|
+
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
|
129
|
+
ruby-saml depends on Nokogiri, and it's possible to use Nokogiri in a dangerous way
|
130
|
+
(by enabling its DTDLOAD option and disabling its NONET option).
|
131
|
+
This dangerous Nokogiri configuration, which is sometimes used by other components,
|
132
|
+
can create an XML External Entity (XXE) vulnerability if the XML data is not trusted.
|
133
|
+
However, ruby-saml never enables this dangerous Nokogiri configuration;
|
134
|
+
ruby-saml never enables DTDLOAD, and it never disables NONET.
|
135
|
+
|
136
|
+
|
126
137
|
## Getting Started
|
127
138
|
In order to use the toolkit you will need to install the gem (either manually or using Bundler), and require the library in your Ruby application:
|
128
139
|
|
@@ -238,7 +249,7 @@ def saml_settings
|
|
238
249
|
settings = OneLogin::RubySaml::Settings.new
|
239
250
|
|
240
251
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
241
|
-
settings.
|
252
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
242
253
|
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
243
254
|
settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
244
255
|
settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
|
@@ -262,6 +273,8 @@ def saml_settings
|
|
262
273
|
end
|
263
274
|
```
|
264
275
|
|
276
|
+
The use of settings.issuer is deprecated in favour of settings.sp_entity_id
|
277
|
+
|
265
278
|
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation` validations by initializing the response with different options:
|
266
279
|
|
267
280
|
```ruby
|
@@ -301,7 +314,7 @@ class SamlController < ApplicationController
|
|
301
314
|
settings = OneLogin::RubySaml::Settings.new
|
302
315
|
|
303
316
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
304
|
-
settings.
|
317
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
305
318
|
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
306
319
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
307
320
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
@@ -364,7 +377,7 @@ def saml_settings
|
|
364
377
|
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
365
378
|
|
366
379
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
367
|
-
settings.
|
380
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
368
381
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
369
382
|
# Optional for most SAML IdPs
|
370
383
|
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
@@ -378,8 +391,8 @@ The following attributes are set:
|
|
378
391
|
* idp_sso_target_url
|
379
392
|
* idp_slo_target_url
|
380
393
|
* idp_attribute_names
|
381
|
-
* idp_cert
|
382
|
-
* idp_cert_fingerprint
|
394
|
+
* idp_cert
|
395
|
+
* idp_cert_fingerprint
|
383
396
|
* idp_cert_multi
|
384
397
|
|
385
398
|
### Retrieve one Entity Descriptor when many exist in Metadata
|
@@ -548,6 +561,8 @@ The settings related to sign are stored in the `security` attribute of the setti
|
|
548
561
|
# Embeded signature or HTTP GET parameter signature
|
549
562
|
# Note that metadata signature is always embedded regardless of this value.
|
550
563
|
settings.security[:embed_sign] = false
|
564
|
+
settings.security[:check_idp_cert_expiration] = false # Enable or not IdP x509 cert expiration check
|
565
|
+
settings.security[:check_sp_cert_expiration] = false # Enable or not SP x509 cert expiration check
|
551
566
|
```
|
552
567
|
|
553
568
|
Notice that the RelayState parameter is used when creating the Signature on the HTTP-Redirect Binding.
|
@@ -117,9 +117,9 @@ module OneLogin
|
|
117
117
|
if settings.assertion_consumer_service_url != nil
|
118
118
|
root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url
|
119
119
|
end
|
120
|
-
if settings.
|
120
|
+
if settings.sp_entity_id != nil
|
121
121
|
issuer = root.add_element "saml:Issuer"
|
122
|
-
issuer.text = settings.
|
122
|
+
issuer.text = settings.sp_entity_id
|
123
123
|
end
|
124
124
|
|
125
125
|
if settings.name_identifier_value_requested != nil
|
@@ -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)
|
@@ -105,9 +105,9 @@ module OneLogin
|
|
105
105
|
root.attributes['Version'] = "2.0"
|
106
106
|
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
107
107
|
|
108
|
-
if settings.
|
108
|
+
if settings.sp_entity_id
|
109
109
|
issuer = root.add_element "saml:Issuer"
|
110
|
-
issuer.text = settings.
|
110
|
+
issuer.text = settings.sp_entity_id
|
111
111
|
end
|
112
112
|
|
113
113
|
nameid = root.add_element "saml:NameID"
|
@@ -163,7 +163,7 @@ module OneLogin
|
|
163
163
|
|
164
164
|
return append_error("No settings on logout response") if settings.nil?
|
165
165
|
|
166
|
-
return append_error("No
|
166
|
+
return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil?
|
167
167
|
|
168
168
|
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
|
169
169
|
return append_error("No fingerprint or certificate on settings of the logout response")
|
@@ -228,13 +228,19 @@ module OneLogin
|
|
228
228
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
229
229
|
)
|
230
230
|
|
231
|
+
expired = false
|
231
232
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
232
233
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
233
|
-
:cert =>
|
234
|
+
:cert => idp_cert,
|
234
235
|
:sig_alg => options[:get_params]['SigAlg'],
|
235
236
|
:signature => options[:get_params]['Signature'],
|
236
237
|
:query_string => query_string
|
237
238
|
)
|
239
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
240
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
241
|
+
expired = true
|
242
|
+
end
|
243
|
+
end
|
238
244
|
else
|
239
245
|
valid = false
|
240
246
|
idp_certs[:signing].each do |signing_idp_cert|
|
@@ -245,11 +251,20 @@ module OneLogin
|
|
245
251
|
:query_string => query_string
|
246
252
|
)
|
247
253
|
if valid
|
254
|
+
if settings.security[:check_idp_cert_expiration]
|
255
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
256
|
+
expired = true
|
257
|
+
end
|
258
|
+
end
|
248
259
|
break
|
249
260
|
end
|
250
261
|
end
|
251
262
|
end
|
252
263
|
|
264
|
+
if expired
|
265
|
+
error_msg = "IdP x509 certificate expired"
|
266
|
+
return append_error(error_msg)
|
267
|
+
end
|
253
268
|
unless valid
|
254
269
|
error_msg = "Invalid Signature on Logout Response"
|
255
270
|
return append_error(error_msg)
|
@@ -57,8 +57,8 @@ module OneLogin
|
|
57
57
|
end
|
58
58
|
|
59
59
|
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
60
|
-
if settings.
|
61
|
-
root.attributes["entityID"] = settings.
|
60
|
+
if settings.sp_entity_id
|
61
|
+
root.attributes["entityID"] = settings.sp_entity_id
|
62
62
|
end
|
63
63
|
if settings.single_logout_service_url
|
64
64
|
sp_sso.add_element "md:SingleLogoutService", {
|
@@ -589,11 +589,11 @@ module OneLogin
|
|
589
589
|
# @raise [ValidationError] if soft == false and validation fails
|
590
590
|
#
|
591
591
|
def validate_audience
|
592
|
-
return true if audiences.empty? || settings.
|
592
|
+
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
593
593
|
|
594
|
-
unless audiences.include? settings.
|
594
|
+
unless audiences.include? settings.sp_entity_id
|
595
595
|
s = audiences.count > 1 ? 's' : '';
|
596
|
-
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.
|
596
|
+
error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
|
597
597
|
return append_error(error_msg)
|
598
598
|
end
|
599
599
|
|
@@ -781,8 +781,8 @@ module OneLogin
|
|
781
781
|
return append_error("An empty NameID value found")
|
782
782
|
end
|
783
783
|
|
784
|
-
unless settings.
|
785
|
-
if name_id_spnamequalifier != settings.
|
784
|
+
unless settings.sp_entity_id.nil? || settings.sp_entity_id.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
|
785
|
+
if name_id_spnamequalifier != settings.sp_entity_id
|
786
786
|
return append_error("The SPNameQualifier value mistmatch the SP entityID value.")
|
787
787
|
end
|
788
788
|
end
|
@@ -829,24 +829,43 @@ module OneLogin
|
|
829
829
|
return append_error(error_msg)
|
830
830
|
end
|
831
831
|
|
832
|
+
|
832
833
|
idp_certs = settings.get_idp_cert_multi
|
833
834
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
834
835
|
opts = {}
|
835
836
|
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
|
836
|
-
|
837
|
+
idp_cert = settings.get_idp_cert
|
837
838
|
fingerprint = settings.get_fingerprint
|
839
|
+
opts[:cert] = idp_cert
|
838
840
|
|
839
|
-
|
841
|
+
if fingerprint && doc.validate_document(fingerprint, @soft, opts)
|
842
|
+
if settings.security[:check_idp_cert_expiration]
|
843
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
844
|
+
error_msg = "IdP x509 certificate expired"
|
845
|
+
return append_error(error_msg)
|
846
|
+
end
|
847
|
+
end
|
848
|
+
else
|
840
849
|
return append_error(error_msg)
|
841
850
|
end
|
842
851
|
else
|
843
852
|
valid = false
|
853
|
+
expired = false
|
844
854
|
idp_certs[:signing].each do |idp_cert|
|
845
855
|
valid = doc.validate_document_with_cert(idp_cert)
|
846
856
|
if valid
|
857
|
+
if settings.security[:check_idp_cert_expiration]
|
858
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
859
|
+
expired = true
|
860
|
+
end
|
861
|
+
end
|
847
862
|
break
|
848
863
|
end
|
849
864
|
end
|
865
|
+
if expired
|
866
|
+
error_msg = "IdP x509 certificate expired"
|
867
|
+
return append_error(error_msg)
|
868
|
+
end
|
850
869
|
unless valid
|
851
870
|
return append_error(error_msg)
|
852
871
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "xml_security"
|
2
2
|
require "onelogin/ruby-saml/attribute_service"
|
3
3
|
require "onelogin/ruby-saml/utils"
|
4
|
+
require "onelogin/ruby-saml/validation_error"
|
4
5
|
|
5
6
|
# Only supports SAML 2.0
|
6
7
|
module OneLogin
|
@@ -40,7 +41,6 @@ module OneLogin
|
|
40
41
|
attr_accessor :idp_name_qualifier
|
41
42
|
attr_accessor :valid_until
|
42
43
|
# SP Data
|
43
|
-
attr_accessor :issuer
|
44
44
|
attr_accessor :assertion_consumer_service_url
|
45
45
|
attr_accessor :assertion_consumer_service_binding
|
46
46
|
attr_accessor :sp_name_qualifier
|
@@ -68,6 +68,28 @@ module OneLogin
|
|
68
68
|
# Compability
|
69
69
|
attr_accessor :assertion_consumer_logout_service_url
|
70
70
|
attr_accessor :assertion_consumer_logout_service_binding
|
71
|
+
attr_accessor :issuer
|
72
|
+
|
73
|
+
# @return [String] SP Entity ID
|
74
|
+
#
|
75
|
+
def sp_entity_id
|
76
|
+
val = nil
|
77
|
+
if @sp_entity_id.nil?
|
78
|
+
if @issuer
|
79
|
+
val = @issuer
|
80
|
+
end
|
81
|
+
else
|
82
|
+
val = @sp_entity_id
|
83
|
+
end
|
84
|
+
val
|
85
|
+
end
|
86
|
+
|
87
|
+
# Setter for SP Entity ID.
|
88
|
+
# @param val [String].
|
89
|
+
#
|
90
|
+
def sp_entity_id=(val)
|
91
|
+
@sp_entity_id = val
|
92
|
+
end
|
71
93
|
|
72
94
|
# @return [String] Single Logout Service URL.
|
73
95
|
#
|
@@ -167,7 +189,15 @@ module OneLogin
|
|
167
189
|
return nil if certificate.nil? || certificate.empty?
|
168
190
|
|
169
191
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
170
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
192
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
193
|
+
|
194
|
+
if security[:check_sp_cert_expiration]
|
195
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
196
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
cert
|
171
201
|
end
|
172
202
|
|
173
203
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
@@ -197,6 +227,7 @@ module OneLogin
|
|
197
227
|
:compress_request => true,
|
198
228
|
:compress_response => true,
|
199
229
|
:soft => true,
|
230
|
+
:double_quote_xml_attribute_values => false,
|
200
231
|
:security => {
|
201
232
|
:authn_requests_signed => false,
|
202
233
|
:logout_requests_signed => false,
|
@@ -207,9 +238,10 @@ module OneLogin
|
|
207
238
|
:metadata_signed => false,
|
208
239
|
:embed_sign => false,
|
209
240
|
:digest_method => XMLSecurity::Document::SHA1,
|
210
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
211
|
-
|
212
|
-
|
241
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
242
|
+
:check_idp_cert_expiration => false,
|
243
|
+
:check_sp_cert_expiration => false
|
244
|
+
}.freeze
|
213
245
|
}.freeze
|
214
246
|
end
|
215
247
|
end
|
@@ -280,13 +280,19 @@ module OneLogin
|
|
280
280
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
281
281
|
)
|
282
282
|
|
283
|
+
expired = false
|
283
284
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
284
285
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
285
|
-
:cert =>
|
286
|
+
:cert => idp_cert,
|
286
287
|
:sig_alg => options[:get_params]['SigAlg'],
|
287
288
|
:signature => options[:get_params]['Signature'],
|
288
289
|
:query_string => query_string
|
289
290
|
)
|
291
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
292
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
293
|
+
expired = true
|
294
|
+
end
|
295
|
+
end
|
290
296
|
else
|
291
297
|
valid = false
|
292
298
|
idp_certs[:signing].each do |signing_idp_cert|
|
@@ -297,11 +303,20 @@ module OneLogin
|
|
297
303
|
:query_string => query_string
|
298
304
|
)
|
299
305
|
if valid
|
306
|
+
if settings.security[:check_idp_cert_expiration]
|
307
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
308
|
+
expired = true
|
309
|
+
end
|
310
|
+
end
|
300
311
|
break
|
301
312
|
end
|
302
313
|
end
|
303
314
|
end
|
304
315
|
|
316
|
+
if expired
|
317
|
+
error_msg = "IdP x509 certificate expired"
|
318
|
+
return append_error(error_msg)
|
319
|
+
end
|
305
320
|
unless valid
|
306
321
|
return append_error("Invalid Signature on Logout Request")
|
307
322
|
end
|
@@ -114,9 +114,9 @@ module OneLogin
|
|
114
114
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
115
115
|
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
116
116
|
|
117
|
-
if settings.
|
117
|
+
if settings.sp_entity_id != nil
|
118
118
|
issuer = root.add_element "saml:Issuer"
|
119
|
-
issuer.text = settings.
|
119
|
+
issuer.text = settings.sp_entity_id
|
120
120
|
end
|
121
121
|
|
122
122
|
# add success message
|
@@ -3,6 +3,7 @@ if RUBY_VERSION < '1.9'
|
|
3
3
|
else
|
4
4
|
require 'securerandom'
|
5
5
|
end
|
6
|
+
require "openssl"
|
6
7
|
|
7
8
|
module OneLogin
|
8
9
|
module RubySaml
|
@@ -15,6 +16,18 @@ module OneLogin
|
|
15
16
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
16
17
|
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
17
18
|
|
19
|
+
# Checks if the x509 cert provided is expired
|
20
|
+
#
|
21
|
+
# @param cert [Certificate] The x509 certificate
|
22
|
+
#
|
23
|
+
def self.is_cert_expired(cert)
|
24
|
+
if cert.is_a?(String)
|
25
|
+
cert = OpenSSL::X509::Certificate.new(cert)
|
26
|
+
end
|
27
|
+
|
28
|
+
return cert.not_after < Time.now
|
29
|
+
end
|
30
|
+
|
18
31
|
# Return a properly formatted x509 certificate
|
19
32
|
#
|
20
33
|
# @param cert [String] The original certificate
|
data/ruby-saml.gemspec
CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
]
|
18
18
|
s.files = `git ls-files`.split("\n")
|
19
19
|
s.homepage = %q{http://github.com/onelogin/ruby-saml}
|
20
|
-
s.rubyforge_project = %q{http://www.rubygems.org/gems/ruby-saml}
|
21
20
|
s.rdoc_options = ["--charset=UTF-8"]
|
22
21
|
s.require_paths = ["lib"]
|
23
22
|
s.rubygems_version = %q{1.3.7}
|
@@ -44,6 +43,7 @@ Gem::Specification.new do |s|
|
|
44
43
|
s.add_runtime_dependency('nokogiri', '>= 1.5.10')
|
45
44
|
end
|
46
45
|
|
46
|
+
s.add_development_dependency('coveralls')
|
47
47
|
s.add_development_dependency('minitest', '~> 5.5')
|
48
48
|
s.add_development_dependency('mocha', '~> 0.14')
|
49
49
|
s.add_development_dependency('rake', '~> 10')
|
data/test/logoutresponse_test.rb
CHANGED
@@ -62,7 +62,7 @@ class RubySamlTest < Minitest::Test
|
|
62
62
|
|
63
63
|
assert logoutresponse.validate
|
64
64
|
|
65
|
-
assert_equal settings.
|
65
|
+
assert_equal settings.sp_entity_id, logoutresponse.issuer
|
66
66
|
assert_equal in_relation_to_request_id, logoutresponse.in_response_to
|
67
67
|
|
68
68
|
assert logoutresponse.success?
|
@@ -123,12 +123,13 @@ class RubySamlTest < Minitest::Test
|
|
123
123
|
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired"
|
124
124
|
end
|
125
125
|
|
126
|
-
it "invalidate logout response when in lack of
|
126
|
+
it "invalidate logout response when in lack of sp_entity_id setting" do
|
127
127
|
bad_settings = settings
|
128
128
|
bad_settings.issuer = nil
|
129
|
+
bad_settings.sp_entity_id = nil
|
129
130
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, bad_settings)
|
130
131
|
assert !logoutresponse.validate
|
131
|
-
assert_includes logoutresponse.errors, "No
|
132
|
+
assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response"
|
132
133
|
end
|
133
134
|
|
134
135
|
it "invalidate logout response with wrong issuer" do
|
@@ -139,7 +140,7 @@ class RubySamlTest < Minitest::Test
|
|
139
140
|
assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
|
140
141
|
end
|
141
142
|
|
142
|
-
it "collect errors when collect_errors=true" do
|
143
|
+
it "collect errors when collect_errors=true" do
|
143
144
|
settings.idp_entity_id = 'http://invalid.issuer.example.com/'
|
144
145
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
145
146
|
collect_errors = true
|
@@ -184,7 +185,7 @@ class RubySamlTest < Minitest::Test
|
|
184
185
|
opts = { :matches_request_id => expected_request_id}
|
185
186
|
|
186
187
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings, opts)
|
187
|
-
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
|
188
|
+
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
|
188
189
|
assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}"
|
189
190
|
end
|
190
191
|
|
@@ -202,11 +203,12 @@ class RubySamlTest < Minitest::Test
|
|
202
203
|
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
|
203
204
|
end
|
204
205
|
|
205
|
-
it "raise validation error when in lack of
|
206
|
+
it "raise validation error when in lack of sp_entity_id setting" do
|
206
207
|
settings.issuer = nil
|
208
|
+
settings.sp_entity_id = nil
|
207
209
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
208
210
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
|
209
|
-
assert_includes logoutresponse.errors, "No
|
211
|
+
assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response"
|
210
212
|
end
|
211
213
|
|
212
214
|
it "raise validation error when logout response with wrong issuer" do
|
@@ -392,6 +394,21 @@ class RubySamlTest < Minitest::Test
|
|
392
394
|
assert logoutresponse_sign_test.send(:validate_signature)
|
393
395
|
end
|
394
396
|
|
397
|
+
it "return false when cert expired and check_idp_cert_expiration expired" do
|
398
|
+
params['RelayState'] = params[:RelayState]
|
399
|
+
options = {}
|
400
|
+
options[:get_params] = params
|
401
|
+
settings.security[:check_idp_cert_expiration] = true
|
402
|
+
settings.idp_cert = nil
|
403
|
+
settings.idp_cert_multi = {
|
404
|
+
:signing => [ruby_saml_cert_text],
|
405
|
+
:encryption => []
|
406
|
+
}
|
407
|
+
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
|
408
|
+
assert !logoutresponse_sign_test.send(:validate_signature)
|
409
|
+
assert_includes logoutresponse_sign_test.errors, "IdP x509 certificate expired"
|
410
|
+
end
|
411
|
+
|
395
412
|
it "return false when none cert on idp_cert_multi is valid" do
|
396
413
|
params['RelayState'] = params[:RelayState]
|
397
414
|
options = {}
|
@@ -402,6 +419,7 @@ class RubySamlTest < Minitest::Test
|
|
402
419
|
}
|
403
420
|
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
|
404
421
|
assert !logoutresponse_sign_test.send(:validate_signature)
|
422
|
+
assert_includes logoutresponse_sign_test.errors, "Invalid Signature on Logout Response"
|
405
423
|
end
|
406
424
|
end
|
407
425
|
end
|
data/test/metadata_test.rb
CHANGED
@@ -12,7 +12,7 @@ class MetadataTest < Minitest::Test
|
|
12
12
|
let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
|
13
13
|
|
14
14
|
before do
|
15
|
-
settings.
|
15
|
+
settings.sp_entity_id = "https://example.com"
|
16
16
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
17
17
|
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
18
18
|
end
|
data/test/response_test.rb
CHANGED
@@ -259,10 +259,10 @@ class RubySamlTest < Minitest::Test
|
|
259
259
|
|
260
260
|
it "raise when there is no valid audience" do
|
261
261
|
settings.idp_cert_fingerprint = signature_fingerprint_1
|
262
|
-
settings.
|
262
|
+
settings.sp_entity_id = 'invalid'
|
263
263
|
response_valid_signed.settings = settings
|
264
264
|
response_valid_signed.soft = false
|
265
|
-
error_msg = generate_audience_error(response_valid_signed.settings.
|
265
|
+
error_msg = generate_audience_error(response_valid_signed.settings.sp_entity_id, ['https://someone.example.com/audience'])
|
266
266
|
assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
|
267
267
|
response_valid_signed.is_valid?
|
268
268
|
end
|
@@ -415,11 +415,11 @@ class RubySamlTest < Minitest::Test
|
|
415
415
|
|
416
416
|
it "return false when there is no valid audience" do
|
417
417
|
settings.idp_cert_fingerprint = signature_fingerprint_1
|
418
|
-
settings.
|
418
|
+
settings.sp_entity_id = 'invalid'
|
419
419
|
response_valid_signed.settings = settings
|
420
420
|
response_valid_signed.is_valid?
|
421
421
|
|
422
|
-
assert_includes response_valid_signed.errors, generate_audience_error(response_valid_signed.settings.
|
422
|
+
assert_includes response_valid_signed.errors, generate_audience_error(response_valid_signed.settings.sp_entity_id, ['https://someone.example.com/audience'])
|
423
423
|
end
|
424
424
|
|
425
425
|
it "return false when no ID present in the SAML Response" do
|
@@ -451,7 +451,7 @@ class RubySamlTest < Minitest::Test
|
|
451
451
|
|
452
452
|
it "collect errors when collect_errors=true" do
|
453
453
|
settings.idp_cert = ruby_saml_cert_text
|
454
|
-
settings.
|
454
|
+
settings.sp_entity_id = 'invalid'
|
455
455
|
response_invalid_subjectconfirmation_recipient.settings = settings
|
456
456
|
collect_errors = true
|
457
457
|
response_invalid_subjectconfirmation_recipient.is_valid?(collect_errors)
|
@@ -464,23 +464,23 @@ class RubySamlTest < Minitest::Test
|
|
464
464
|
describe "#validate_audience" do
|
465
465
|
it "return true when the audience is valid" do
|
466
466
|
response.settings = settings
|
467
|
-
response.settings.
|
467
|
+
response.settings.sp_entity_id = '{audience}'
|
468
468
|
assert response.send(:validate_audience)
|
469
469
|
assert_empty response.errors
|
470
470
|
end
|
471
471
|
|
472
472
|
it "return true when the audience is self closing" do
|
473
473
|
response_audience_self_closed.settings = settings
|
474
|
-
response_audience_self_closed.settings.
|
474
|
+
response_audience_self_closed.settings.sp_entity_id = '{audience}'
|
475
475
|
assert response_audience_self_closed.send(:validate_audience)
|
476
476
|
assert_empty response_audience_self_closed.errors
|
477
477
|
end
|
478
478
|
|
479
479
|
it "return false when the audience is valid" do
|
480
480
|
response.settings = settings
|
481
|
-
response.settings.
|
481
|
+
response.settings.sp_entity_id = 'invalid_audience'
|
482
482
|
assert !response.send(:validate_audience)
|
483
|
-
assert_includes response.errors, generate_audience_error(response.settings.
|
483
|
+
assert_includes response.errors, generate_audience_error(response.settings.sp_entity_id, ['{audience}'])
|
484
484
|
end
|
485
485
|
end
|
486
486
|
|
@@ -665,23 +665,23 @@ class RubySamlTest < Minitest::Test
|
|
665
665
|
describe "#validate_audience" do
|
666
666
|
it "return true when the audience is valid" do
|
667
667
|
response_valid_signed.settings = settings
|
668
|
-
response_valid_signed.settings.
|
668
|
+
response_valid_signed.settings.sp_entity_id = "https://someone.example.com/audience"
|
669
669
|
assert response_valid_signed.send(:validate_audience)
|
670
670
|
assert_empty response_valid_signed.errors
|
671
671
|
end
|
672
672
|
|
673
|
-
it "return true when there is not
|
673
|
+
it "return true when there is not sp_entity_id defined" do
|
674
674
|
response_valid_signed.settings = settings
|
675
|
-
response_valid_signed.settings.
|
675
|
+
response_valid_signed.settings.sp_entity_id = nil
|
676
676
|
assert response_valid_signed.send(:validate_audience)
|
677
677
|
assert_empty response_valid_signed.errors
|
678
678
|
end
|
679
679
|
|
680
680
|
it "return false when there is no valid audience" do
|
681
681
|
response_invalid_audience.settings = settings
|
682
|
-
response_invalid_audience.settings.
|
682
|
+
response_invalid_audience.settings.sp_entity_id = "https://invalid.example.com/audience"
|
683
683
|
assert !response_invalid_audience.send(:validate_audience)
|
684
|
-
assert_includes response_invalid_audience.errors, generate_audience_error(response_invalid_audience.settings.
|
684
|
+
assert_includes response_invalid_audience.errors, generate_audience_error(response_invalid_audience.settings.sp_entity_id, ['http://invalid.audience.com'])
|
685
685
|
end
|
686
686
|
end
|
687
687
|
|
@@ -880,6 +880,15 @@ class RubySamlTest < Minitest::Test
|
|
880
880
|
assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response"
|
881
881
|
end
|
882
882
|
|
883
|
+
it "return false when cert expired and check_idp_cert_expiration enabled" do
|
884
|
+
settings.idp_cert_fingerprint = nil
|
885
|
+
settings.idp_cert = ruby_saml_cert_text
|
886
|
+
settings.security[:check_idp_cert_expiration] = true
|
887
|
+
response_valid_signed.settings = settings
|
888
|
+
assert !response_valid_signed.send(:validate_signature)
|
889
|
+
assert_includes response_valid_signed.errors, "IdP x509 certificate expired"
|
890
|
+
end
|
891
|
+
|
883
892
|
it "return false when no X509Certificate and the cert provided at settings mismatches" do
|
884
893
|
settings.idp_cert_fingerprint = nil
|
885
894
|
settings.idp_cert = signature_1
|
@@ -953,7 +962,7 @@ class RubySamlTest < Minitest::Test
|
|
953
962
|
end
|
954
963
|
|
955
964
|
it "return false when wrong_spnamequalifier" do
|
956
|
-
settings.
|
965
|
+
settings.sp_entity_id = 'sp_entity_id'
|
957
966
|
response_wrong_spnamequalifier.settings = settings
|
958
967
|
assert !response_wrong_spnamequalifier.send(:validate_name_id)
|
959
968
|
assert_includes response_wrong_spnamequalifier.errors, "The SPNameQualifier value mistmatch the SP entityID value."
|
@@ -966,7 +975,7 @@ class RubySamlTest < Minitest::Test
|
|
966
975
|
end
|
967
976
|
|
968
977
|
it "return true when nameid is valid and response_wrong_spnamequalifier matches the SP issuer" do
|
969
|
-
settings.
|
978
|
+
settings.sp_entity_id = 'wrong-sp-entityid'
|
970
979
|
response_wrong_spnamequalifier.settings = settings
|
971
980
|
assert response_wrong_spnamequalifier.send(:validate_name_id)
|
972
981
|
end
|
@@ -1398,7 +1407,7 @@ class RubySamlTest < Minitest::Test
|
|
1398
1407
|
|
1399
1408
|
before do
|
1400
1409
|
settings.idp_cert_fingerprint = 'EE:17:4E:FB:A8:81:71:12:0D:2A:78:43:BC:E7:0C:07:58:79:F4:F4'
|
1401
|
-
settings.
|
1410
|
+
settings.sp_entity_id = 'http://rubysaml.com:3000/saml/metadata'
|
1402
1411
|
settings.assertion_consumer_service_url = 'http://rubysaml.com:3000/saml/acs'
|
1403
1412
|
settings.certificate = ruby_saml_cert_text
|
1404
1413
|
settings.private_key = ruby_saml_key_text
|
data/test/settings_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
2
|
|
3
3
|
require 'onelogin/ruby-saml/settings'
|
4
|
+
require 'onelogin/ruby-saml/validation_error'
|
4
5
|
|
5
6
|
class SettingsTest < Minitest::Test
|
6
7
|
|
@@ -243,6 +244,13 @@ class SettingsTest < Minitest::Test
|
|
243
244
|
}
|
244
245
|
end
|
245
246
|
|
247
|
+
it "raises an error if SP certificate expired and check_sp_cert_expiration enabled" do
|
248
|
+
@settings.certificate = ruby_saml_cert_text
|
249
|
+
@settings.security[:check_sp_cert_expiration] = true
|
250
|
+
assert_raises(OneLogin::RubySaml::ValidationError) {
|
251
|
+
settings.get_sp_cert
|
252
|
+
}
|
253
|
+
end
|
246
254
|
end
|
247
255
|
|
248
256
|
describe "#get_sp_cert_new" do
|
@@ -429,6 +429,23 @@ class RubySamlTest < Minitest::Test
|
|
429
429
|
assert logout_request_sign_test.send(:validate_signature)
|
430
430
|
end
|
431
431
|
|
432
|
+
it "return false when cert expired and check_idp_cert_expiration expired" do
|
433
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
434
|
+
params['RelayState'] = params[:RelayState]
|
435
|
+
options = {}
|
436
|
+
options[:get_params] = params
|
437
|
+
settings.security[:check_idp_cert_expiration] = true
|
438
|
+
logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options)
|
439
|
+
settings.idp_cert = nil
|
440
|
+
settings.idp_cert_multi = {
|
441
|
+
:signing => [ruby_saml_cert_text],
|
442
|
+
:encryption => []
|
443
|
+
}
|
444
|
+
logout_request_sign_test.settings = settings
|
445
|
+
assert !logout_request_sign_test.send(:validate_signature)
|
446
|
+
assert_includes logout_request_sign_test.errors, "IdP x509 certificate expired"
|
447
|
+
end
|
448
|
+
|
432
449
|
it "return false when none cert on idp_cert_multi is valid" do
|
433
450
|
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
434
451
|
params['RelayState'] = params[:RelayState]
|
@@ -442,6 +459,8 @@ class RubySamlTest < Minitest::Test
|
|
442
459
|
}
|
443
460
|
logout_request_sign_test.settings = settings
|
444
461
|
assert !logout_request_sign_test.send(:validate_signature)
|
462
|
+
assert_includes logout_request_sign_test.errors, "Invalid Signature on Logout Request"
|
463
|
+
|
445
464
|
end
|
446
465
|
end
|
447
466
|
end
|
data/test/test_helper.rb
CHANGED
data/test/xml_security_test.rb
CHANGED
@@ -239,7 +239,7 @@ class XmlSecurityTest < Minitest::Test
|
|
239
239
|
settings.idp_sso_target_url = "https://idp.example.com/sso"
|
240
240
|
settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
241
241
|
settings.idp_slo_target_url = "https://idp.example.com/slo",
|
242
|
-
settings.
|
242
|
+
settings.sp_entity_id = "https://sp.example.com/saml2"
|
243
243
|
settings.assertion_consumer_service_url = "https://sp.example.com/acs"
|
244
244
|
settings.single_logout_service_url = "https://sp.example.com/sls"
|
245
245
|
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: 1.
|
4
|
+
version: 1.11.0
|
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-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.5.10
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: coveralls
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: minitest
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -344,8 +358,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
344
358
|
- !ruby/object:Gem::Version
|
345
359
|
version: '0'
|
346
360
|
requirements: []
|
347
|
-
rubyforge_project:
|
348
|
-
rubygems_version: 2.
|
361
|
+
rubyforge_project:
|
362
|
+
rubygems_version: 2.5.2.1
|
349
363
|
signing_key:
|
350
364
|
specification_version: 4
|
351
365
|
summary: SAML Ruby Tookit
|