devise_saml_authenticatable 1.6.3 → 1.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f648472eaaf23e5e668e51f84fadff2354879045f0ec798a383dd8a2e2ee135a
4
- data.tar.gz: 443a5e883595f8baa2297e2ca173e8f97ae0abf3502538ce1d0f4e0e1c84081e
3
+ metadata.gz: 23fdb33308c8d98c67e3fe7d5654bfcdc7afe40c2276822a3936785ed29e15ea
4
+ data.tar.gz: 11569096a198fb51b129d82eaab03a870c3c8983b7dfe88aa0bcc53f1f4fa2ce
5
5
  SHA512:
6
- metadata.gz: 4765fe0a60d2a8ffd97d30d5b0d2fdb55d70a56bd9cb91f760ef7862a0d9ec80570da5d4758a784ac552232e1e5893fdd1accead2ff677893b0eb4a3500dcb9c
7
- data.tar.gz: fcd0e6f70b75fdb9b12b3c1954899989e26f8ddb874c6222ab59ca460387a9672ab7767c13855bc4c3cbabd14fdcdb3ddabb2478412dbb452a17194c20ff4c32
6
+ metadata.gz: 605f76c64fa08cb1ec9f26224af74ccec6c8d85b507899e6f61f1c65e8ada6b7672b6b9398d78fcfbd075d8c7754e824e0a8ed822001b124a6658e862b72203c
7
+ data.tar.gz: 0f8bb1f715288790cffb1b20214d6c0da71c546aaa09e626183b86d1274c5d6e3aae90dea8af6c4d572c6f2131360c948203a9a14706ed7414c685833eac173d
@@ -0,0 +1,76 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+ branches:
8
+ - master
9
+ jobs:
10
+ test:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby:
15
+ - "2.7"
16
+ - "2.6"
17
+ - "2.5"
18
+ - "2.4"
19
+ - "2.3"
20
+ gemfile:
21
+ - Gemfile
22
+ - spec/support/Gemfile.rails6
23
+ - spec/support/Gemfile.rails5.2
24
+ - spec/support/Gemfile.rails5.1
25
+ - spec/support/Gemfile.rails5
26
+ bundler:
27
+ - "2"
28
+ exclude:
29
+ - ruby: "2.3"
30
+ gemfile: Gemfile
31
+ bundler: "2"
32
+ - ruby: "2.3"
33
+ gemfile: spec/support/Gemfile.rails6
34
+ bundler: "2"
35
+ - ruby: "2.4"
36
+ gemfile: Gemfile
37
+ bundler: "2"
38
+ - ruby: "2.4"
39
+ gemfile: spec/support/Gemfile.rails6
40
+ bundler: "2"
41
+ include:
42
+ - ruby: "2.5"
43
+ gemfile: spec/support/Gemfile.rails4
44
+ bundler: "1"
45
+ - ruby: "2.4"
46
+ gemfile: spec/support/Gemfile.rails4
47
+ bundler: "1"
48
+ - ruby: "2.3"
49
+ gemfile: spec/support/Gemfile.rails4
50
+ bundler: "1"
51
+ - ruby: "2.2"
52
+ gemfile: spec/support/Gemfile.rails5.1
53
+ bundler: "1"
54
+ - ruby: "2.2"
55
+ gemfile: spec/support/Gemfile.rails5
56
+ bundler: "1"
57
+ - ruby: "2.2"
58
+ gemfile: spec/support/Gemfile.rails4
59
+ bundler: "1"
60
+ - ruby: "2.1"
61
+ gemfile: spec/support/Gemfile.rails4
62
+ bundler: "1"
63
+ - ruby: "2.0"
64
+ gemfile: spec/support/Gemfile.rails4
65
+ bundler: "1"
66
+ runs-on: ubuntu-latest
67
+ env:
68
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
69
+ steps:
70
+ - uses: actions/checkout@v2
71
+ - uses: ruby/setup-ruby@v1
72
+ with:
73
+ bundler: ${{ matrix.bundler }}
74
+ ruby-version: ${{ matrix.ruby }}
75
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
76
+ - run: bundle exec rake
data/README.md CHANGED
@@ -57,8 +57,8 @@ An extra step in SAML SSO setup is adding your application to your identity prov
57
57
  Your IdP should give you some information you need to configure in [ruby-saml](https://github.com/onelogin/ruby-saml), as in the next section:
58
58
 
59
59
  - Issuer (`idp_entity_id`)
60
- - SSO endpoint (`idp_sso_target_url`)
61
- - SLO endpoint (`idp_slo_target_url`)
60
+ - SSO endpoint (`idp_sso_service_url`)
61
+ - SLO endpoint (`idp_slo_service_url`)
62
62
  - Certificate fingerprint (`idp_cert_fingerprint`) and algorithm (`idp_cert_fingerprint_algorithm`)
63
63
  - Or the certificate itself (`idp_cert`)
64
64
 
@@ -111,6 +111,10 @@ In `config/initializers/devise.rb`:
111
111
  # This is a time in seconds.
112
112
  # config.allowed_clock_drift_in_seconds = 0
113
113
 
114
+ # In SAML responses, validate that the identity provider has included an InResponseTo
115
+ # header that matches the ID of the SAML request. (Default is false)
116
+ # config.saml_validate_in_response_to = false
117
+
114
118
  # Configure with your SAML settings (see ruby-saml's README for more information: https://github.com/onelogin/ruby-saml).
115
119
  config.saml_configure do |settings|
116
120
  # assertion_consumer_service_url is required starting with ruby-saml 1.4.3: https://github.com/onelogin/ruby-saml#updating-from-142-to-143
@@ -119,8 +123,8 @@ In `config/initializers/devise.rb`:
119
123
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
120
124
  settings.issuer = "http://localhost:3000/saml/metadata"
121
125
  settings.authn_context = ""
122
- settings.idp_slo_target_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
123
- settings.idp_sso_target_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
126
+ settings.idp_slo_service_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
127
+ settings.idp_sso_service_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
124
128
  settings.idp_cert_fingerprint = "00:A1:2B:3C:44:55:6F:A7:88:CC:DD:EE:22:33:44:55:D6:77:8F:99"
125
129
  settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
126
130
  end
@@ -207,8 +211,8 @@ class IdPSettingsAdapter
207
211
  issuer: "http://localhost:3000/saml/metadata",
208
212
  idp_entity_id: "http://www.example_idp_entity_id.com",
209
213
  authn_context: "",
210
- idp_slo_target_url: "http://example_idp_slo_target_url.com",
211
- idp_sso_target_url: "http://example_idp_sso_target_url.com",
214
+ idp_slo_service_url: "http://example_idp_slo_service_url.com",
215
+ idp_sso_service_url: "http://example_idp_sso_service_url.com",
212
216
  idp_cert: "example_idp_cert"
213
217
  }
214
218
  when "http://www.another_idp_entity_id.biz"
@@ -219,8 +223,8 @@ class IdPSettingsAdapter
219
223
  issuer: "http://localhost:3000/saml/metadata",
220
224
  idp_entity_id: "http://www.another_idp_entity_id.biz",
221
225
  authn_context: "",
222
- idp_slo_target_url: "http://another_idp_slo_target_url.com",
223
- idp_sso_target_url: "http://another_idp_sso_target_url.com",
226
+ idp_slo_service_url: "http://another_idp_slo_service_url.com",
227
+ idp_sso_service_url: "http://another_idp_sso_service_url.com",
224
228
  idp_cert: "another_idp_cert"
225
229
  }
226
230
  else
@@ -5,10 +5,10 @@ class Devise::SamlSessionsController < Devise::SessionsController
5
5
  unloadable if Rails::VERSION::MAJOR < 4
6
6
  if Rails::VERSION::MAJOR < 5
7
7
  skip_before_filter :verify_authenticity_token
8
- prepend_before_filter :store_info_for_sp_initiated_logout, only: :destroy
8
+ prepend_before_filter :verify_signed_out_user, :store_info_for_sp_initiated_logout, only: :destroy
9
9
  else
10
10
  skip_before_action :verify_authenticity_token, raise: false
11
- prepend_before_action :store_info_for_sp_initiated_logout, only: :destroy
11
+ prepend_before_action :verify_signed_out_user, :store_info_for_sp_initiated_logout, only: :destroy
12
12
  end
13
13
 
14
14
  def new
@@ -16,6 +16,9 @@ class Devise::SamlSessionsController < Devise::SessionsController
16
16
  request = OneLogin::RubySaml::Authrequest.new
17
17
  auth_params = { RelayState: relay_state } if relay_state
18
18
  action = request.create(saml_config(idp_entity_id), auth_params || {})
19
+ if request.respond_to?(:request_id)
20
+ session[:saml_transaction_id] = request.request_id
21
+ end
19
22
  redirect_to action
20
23
  end
21
24
 
@@ -79,6 +82,18 @@ class Devise::SamlSessionsController < Devise::SessionsController
79
82
  request.create(saml_settings)
80
83
  end
81
84
 
85
+ # Overried devise: if user is signed out, not create the SP initiated logout request,
86
+ # redirect to saml_sign_out_success_url,
87
+ # or devise's after_sign_out_path_for
88
+ def verify_signed_out_user
89
+ if all_signed_out?
90
+ set_flash_message! :notice, :already_signed_out
91
+
92
+ redirect_to Devise.saml_sign_out_success_url.presence ||
93
+ Devise::SessionsController.new.after_sign_out_path_for(resource_name)
94
+ end
95
+ end
96
+
82
97
  def generate_idp_logout_response(saml_config, logout_request_id)
83
98
 
84
99
  params = {}
@@ -8,8 +8,7 @@ module Devise
8
8
  if params[:SAMLResponse]
9
9
  OneLogin::RubySaml::Response.new(
10
10
  params[:SAMLResponse],
11
- settings: saml_config(get_idp_entity_id(params)),
12
- allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
11
+ response_options,
13
12
  )
14
13
  else
15
14
  false
@@ -36,8 +35,7 @@ module Devise
36
35
  def parse_saml_response
37
36
  @response = OneLogin::RubySaml::Response.new(
38
37
  params[:SAMLResponse],
39
- settings: saml_config(get_idp_entity_id(params)),
40
- allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
38
+ response_options,
41
39
  )
42
40
  unless @response.is_valid?
43
41
  failed_auth("Auth errors: #{@response.errors.join(', ')}")
@@ -57,6 +55,18 @@ module Devise
57
55
  Devise.saml_failed_callback.new.handle(@response, self) if Devise.saml_failed_callback
58
56
  end
59
57
 
58
+ def response_options
59
+ options = {
60
+ settings: saml_config(get_idp_entity_id(params)),
61
+ allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
62
+ }
63
+
64
+ if Devise.saml_validate_in_response_to
65
+ options[:matches_request_id] = request.session[:saml_transaction_id] || "ID_MISSING"
66
+ end
67
+
68
+ options
69
+ end
60
70
  end
61
71
  end
62
72
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseSamlAuthenticatable
2
- VERSION = "1.6.3"
2
+ VERSION = "1.7.0"
3
3
  end
@@ -67,6 +67,10 @@ module Devise
67
67
  mattr_accessor :saml_relay_state
68
68
  @@saml_relay_state
69
69
 
70
+ # Validate that the InResponseTo header in SAML responses matches the ID of the request.
71
+ mattr_accessor :saml_validate_in_response_to
72
+ @@saml_validate_in_response_to = false
73
+
70
74
  # Instead of storing the attribute_map in attribute-map.yml, store it in the database, or set it programatically
71
75
  mattr_accessor :saml_attribute_map_resolver
72
76
  @@saml_attribute_map_resolver ||= "::DeviseSamlAuthenticatable::DefaultAttributeMapResolver"
@@ -1,4 +1,5 @@
1
1
  require 'rails_helper'
2
+ require 'support/ruby_saml_support'
2
3
 
3
4
  # The important parts from devise
4
5
  class DeviseController < ApplicationController
@@ -8,38 +9,55 @@ class DeviseController < ApplicationController
8
9
  User
9
10
  end
10
11
 
12
+ def resource_name
13
+ "users"
14
+ end
15
+
11
16
  def require_no_authentication
12
17
  end
18
+
19
+ def set_flash_message!(key, kind, options = {})
20
+ flash[key] = I18n.t("devise.sessions.#{kind}")
21
+ end
13
22
  end
23
+
14
24
  class Devise::SessionsController < DeviseController
15
25
  def destroy
16
26
  sign_out
17
27
  redirect_to after_sign_out_path_for(:user)
18
28
  end
19
-
20
- def verify_signed_out_user
21
- # no-op for these tests
22
- end
23
29
  end
24
30
 
25
31
  require_relative '../../../app/controllers/devise/saml_sessions_controller'
26
32
 
27
33
  describe Devise::SamlSessionsController, type: :controller do
34
+ include RubySamlSupport
35
+
28
36
  let(:idp_providers_adapter) { spy("Stub IDPSettings Adaptor") }
29
37
 
30
38
  before do
31
39
  @request.env["devise.mapping"] = Devise.mappings[:user]
32
- allow(idp_providers_adapter).to receive(:settings).and_return({
40
+ settings = {
33
41
  assertion_consumer_service_url: "acs_url",
34
42
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
35
43
  name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
36
44
  issuer: "sp_issuer",
37
45
  idp_entity_id: "http://www.example.com",
38
46
  authn_context: "",
39
- idp_slo_target_url: "http://idp_slo_url",
40
- idp_sso_target_url: "http://idp_sso_url",
41
47
  idp_cert: "idp_cert"
48
+ }
49
+ with_ruby_saml_1_12_or_greater(proc {
50
+ settings.merge!(
51
+ idp_slo_service_url: "http://idp_slo_url",
52
+ idp_sso_service_url: "http://idp_sso_url",
53
+ )
54
+ }, else_do: proc {
55
+ settings.merge!(
56
+ idp_slo_target_url: "http://idp_slo_url",
57
+ idp_sso_target_url: "http://idp_sso_url",
58
+ )
42
59
  })
60
+ allow(idp_providers_adapter).to receive(:settings).and_return(settings)
43
61
  end
44
62
 
45
63
  before do
@@ -72,6 +90,13 @@ describe Devise::SamlSessionsController, type: :controller do
72
90
  do_get
73
91
  expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/auth\?SAMLRequest=))
74
92
  end
93
+
94
+ it "stores saml_transaction_id in the session" do
95
+ do_get
96
+ if OneLogin::RubySaml::Authrequest.public_instance_methods.include?(:request_id)
97
+ expect(session[:saml_transaction_id]).to be_present
98
+ end
99
+ end
75
100
  end
76
101
 
77
102
  context "with a specified idp" do
@@ -84,6 +109,13 @@ describe Devise::SamlSessionsController, type: :controller do
84
109
  expect(response).to redirect_to(%r(\Ahttp://idp_sso_url\?SAMLRequest=))
85
110
  end
86
111
 
112
+ it "stores saml_transaction_id in the session" do
113
+ do_get
114
+ if OneLogin::RubySaml::Authrequest.public_instance_methods.include?(:request_id)
115
+ expect(session[:saml_transaction_id]).to be_present
116
+ end
117
+ end
118
+
87
119
  it "uses the DefaultIdpEntityIdReader" do
88
120
  expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
89
121
  do_get
@@ -172,80 +204,136 @@ describe Devise::SamlSessionsController, type: :controller do
172
204
  end
173
205
 
174
206
  describe '#destroy' do
175
- before do
176
- allow(controller).to receive(:sign_out)
177
- end
207
+ subject { delete :destroy }
178
208
 
179
- context "when using the default saml config" do
180
- it "signs out and redirects to the IdP" do
181
- delete :destroy
182
- expect(controller).to have_received(:sign_out)
183
- expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/logout\?SAMLRequest=))
209
+ context "when user is signed out" do
210
+ before do
211
+ class Devise::SessionsController < DeviseController
212
+ def all_signed_out?
213
+ true
214
+ end
215
+ end
184
216
  end
185
- end
186
217
 
187
- context "when configured to use a non-transient name identifier" do
188
- before do
189
- allow(Devise.saml_config).to receive(:name_identifier_format).and_return("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent")
218
+ shared_examples "not create SP initiated logout request" do
219
+ it do
220
+ expect(OneLogin::RubySaml::Logoutrequest).not_to receive(:new)
221
+ subject
222
+ end
223
+ end
224
+
225
+ context "when Devise.saml_sign_out_success_url is set" do
226
+ before do
227
+ allow(Devise).to receive(:saml_sign_out_success_url).and_return("http://localhost:8009/logged_out")
228
+ end
229
+
230
+ it "redirect to saml_sign_out_success_url" do
231
+ is_expected.to redirect_to "http://localhost:8009/logged_out"
232
+ expect(flash[:notice]).to eq I18n.t("devise.sessions.already_signed_out")
233
+ end
234
+
235
+ it_behaves_like "not create SP initiated logout request"
190
236
  end
191
237
 
192
- it "includes a LogoutRequest with the name identifier and session index", :aggregate_failures do
193
- controller.current_user = Struct.new(:email, :session_index).new("user@example.com", "sessionindex")
238
+ context "when Devise.saml_sign_out_success_url is not set" do
239
+ before do
240
+ class Devise::SessionsController < DeviseController
241
+ def after_sign_out_path_for(_)
242
+ "http://localhost:8009/logged_out"
243
+ end
244
+ end
245
+ end
194
246
 
195
- actual_settings = nil
196
- expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
197
- actual_settings = settings
198
- "http://localhost:8009/saml/logout"
247
+ it "redirect to devise's after sign out path" do
248
+ is_expected.to redirect_to "http://localhost:8009/logged_out"
249
+ expect(flash[:notice]).to eq I18n.t("devise.sessions.already_signed_out")
199
250
  end
200
251
 
201
- delete :destroy
202
- expect(actual_settings.name_identifier_value).to eq("user@example.com")
203
- expect(actual_settings.sessionindex).to eq("sessionindex")
252
+ it_behaves_like "not create SP initiated logout request"
204
253
  end
205
254
  end
206
255
 
207
- context "with a specified idp" do
256
+ context "when user is not signed out" do
208
257
  before do
209
- Devise.idp_settings_adapter = idp_providers_adapter
258
+ class Devise::SessionsController < DeviseController
259
+ def all_signed_out?
260
+ false
261
+ end
262
+ end
263
+ allow(controller).to receive(:sign_out)
210
264
  end
211
265
 
212
- it "redirects to the associated IdP SSO target url" do
213
- expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
214
- delete :destroy
215
- expect(controller).to have_received(:sign_out)
216
- expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
266
+ context "when using the default saml config" do
267
+ it "signs out and redirects to the IdP" do
268
+ delete :destroy
269
+ expect(controller).to have_received(:sign_out)
270
+ expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/logout\?SAMLRequest=))
271
+ end
217
272
  end
218
273
 
219
- context "with a specified idp entity id reader" do
220
- class OurIdpEntityIdReader
221
- def self.entity_id(params)
222
- params[:entity_id]
223
- end
274
+ context "when configured to use a non-transient name identifier" do
275
+ before do
276
+ allow(Devise.saml_config).to receive(:name_identifier_format).and_return("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent")
224
277
  end
225
278
 
226
- subject(:do_delete) {
227
- if Rails::VERSION::MAJOR > 4
228
- delete :destroy, params: {entity_id: "http://www.example.com"}
229
- else
230
- delete :destroy, entity_id: "http://www.example.com"
279
+ it "includes a LogoutRequest with the name identifier and session index", :aggregate_failures do
280
+ controller.current_user = Struct.new(:email, :session_index).new("user@example.com", "sessionindex")
281
+
282
+ actual_settings = nil
283
+ expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
284
+ actual_settings = settings
285
+ "http://localhost:8009/saml/logout"
231
286
  end
232
- }
233
287
 
234
- before do
235
- @default_reader = Devise.idp_entity_id_reader
236
- Devise.idp_entity_id_reader = OurIdpEntityIdReader # which will have some different behavior
288
+ delete :destroy
289
+ expect(actual_settings.name_identifier_value).to eq("user@example.com")
290
+ expect(actual_settings.sessionindex).to eq("sessionindex")
237
291
  end
292
+ end
238
293
 
239
- after do
240
- Devise.idp_entity_id_reader = @default_reader
294
+ context "with a specified idp" do
295
+ before do
296
+ Devise.idp_settings_adapter = idp_providers_adapter
241
297
  end
242
298
 
243
- it "redirects to the associated IdP SLO target url" do
244
- do_delete
299
+ it "redirects to the associated IdP SSO target url" do
300
+ expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
301
+ delete :destroy
245
302
  expect(controller).to have_received(:sign_out)
246
- expect(idp_providers_adapter).to have_received(:settings).with("http://www.example.com")
247
303
  expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
248
304
  end
305
+
306
+ context "with a specified idp entity id reader" do
307
+ class OurIdpEntityIdReader
308
+ def self.entity_id(params)
309
+ params[:entity_id]
310
+ end
311
+ end
312
+
313
+ subject(:do_delete) {
314
+ if Rails::VERSION::MAJOR > 4
315
+ delete :destroy, params: {entity_id: "http://www.example.com"}
316
+ else
317
+ delete :destroy, entity_id: "http://www.example.com"
318
+ end
319
+ }
320
+
321
+ before do
322
+ @default_reader = Devise.idp_entity_id_reader
323
+ Devise.idp_entity_id_reader = OurIdpEntityIdReader # which will have some different behavior
324
+ end
325
+
326
+ after do
327
+ Devise.idp_entity_id_reader = @default_reader
328
+ end
329
+
330
+ it "redirects to the associated IdP SLO target url" do
331
+ do_delete
332
+ expect(controller).to have_received(:sign_out)
333
+ expect(idp_providers_adapter).to have_received(:settings).with("http://www.example.com")
334
+ expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
335
+ end
336
+ end
249
337
  end
250
338
  end
251
339
  end
@@ -1,6 +1,9 @@
1
1
  require 'spec_helper'
2
+ require 'support/ruby_saml_support'
2
3
 
3
4
  describe DeviseSamlAuthenticatable::SamlConfig do
5
+ include RubySamlSupport
6
+
4
7
  let(:saml_config) { controller.saml_config }
5
8
  let(:controller) { Class.new { include DeviseSamlAuthenticatable::SamlConfig }.new }
6
9
 
@@ -26,32 +29,54 @@ describe DeviseSamlAuthenticatable::SamlConfig do
26
29
  let(:saml_config) { controller.saml_config(idp_entity_id) }
27
30
  let(:idp_providers_adapter) {
28
31
  Class.new {
32
+ extend RubySamlSupport
33
+
29
34
  def self.settings(idp_entity_id)
30
35
  #some hash of stuff (by doing a fetch, in our case, but could also be a giant hash keyed by idp_entity_id)
31
36
  if idp_entity_id == "http://www.example.com"
32
- {
37
+ base = {
33
38
  assertion_consumer_service_url: "acs_url",
34
39
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
35
40
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
36
41
  issuer: "sp_issuer",
37
42
  idp_entity_id: "http://www.example.com",
38
43
  authn_context: "",
39
- idp_slo_target_url: "idp_slo_url",
40
- idp_sso_target_url: "idp_sso_url",
41
44
  idp_cert: "idp_cert"
42
45
  }
46
+ with_ruby_saml_1_12_or_greater(proc {
47
+ base.merge!(
48
+ idp_slo_service_url: "idp_slo_url",
49
+ idp_sso_service_url: "idp_sso_url",
50
+ )
51
+ }, else_do: proc {
52
+ base.merge!(
53
+ idp_slo_target_url: "idp_slo_url",
54
+ idp_sso_target_url: "idp_sso_url",
55
+ )
56
+ })
57
+ base
43
58
  elsif idp_entity_id == "http://www.example.com_other"
44
- {
59
+ base = {
45
60
  assertion_consumer_service_url: "acs_url_other",
46
61
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST_other",
47
62
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress_other",
48
63
  issuer: "sp_issuer_other",
49
64
  idp_entity_id: "http://www.example.com_other",
50
65
  authn_context: "_other",
51
- idp_slo_target_url: "idp_slo_url_other",
52
- idp_sso_target_url: "idp_sso_url_other",
53
66
  idp_cert: "idp_cert_other"
54
67
  }
68
+ with_ruby_saml_1_12_or_greater(proc {
69
+ base.merge!(
70
+ idp_slo_service_url: "idp_slo_url_other",
71
+ idp_sso_service_url: "idp_sso_url_other",
72
+ )
73
+ }, else_do: proc {
74
+ base.merge!(
75
+ idp_slo_target_url: "idp_slo_url_other",
76
+ idp_sso_target_url: "idp_sso_url_other",
77
+ )
78
+ })
79
+ base
55
80
  else
56
81
  {}
57
82
  end
@@ -63,7 +88,11 @@ describe DeviseSamlAuthenticatable::SamlConfig do
63
88
  let(:idp_entity_id) { "http://www.example.com" }
64
89
  it "uses the settings from the adapter for that idp" do
65
90
  expect(saml_config.idp_entity_id).to eq (idp_entity_id)
66
- expect(saml_config.idp_sso_target_url).to eq ("idp_sso_url")
91
+ with_ruby_saml_1_12_or_greater(proc {
92
+ expect(saml_config.idp_sso_service_url).to eq('idp_sso_url')
93
+ }, else_do: proc {
94
+ expect(saml_config.idp_sso_target_url).to eq('idp_sso_url')
95
+ })
67
96
  expect(saml_config.class).to eq OneLogin::RubySaml::Settings
68
97
  end
69
98
  end
@@ -72,7 +101,11 @@ describe DeviseSamlAuthenticatable::SamlConfig do
72
101
  let(:idp_entity_id) { "http://www.example.com_other" }
73
102
  it "returns the other idp settings" do
74
103
  expect(saml_config.idp_entity_id).to eq (idp_entity_id)
75
- expect(saml_config.idp_sso_target_url).to eq ("idp_sso_url_other")
104
+ with_ruby_saml_1_12_or_greater(proc {
105
+ expect(saml_config.idp_sso_service_url).to eq('idp_sso_url_other')
106
+ }, else_do: proc {
107
+ expect(saml_config.idp_sso_target_url).to eq('idp_sso_url_other')
108
+ })
76
109
  expect(saml_config.class).to eq OneLogin::RubySaml::Settings
77
110
  end
78
111
  end
@@ -80,11 +113,8 @@ describe DeviseSamlAuthenticatable::SamlConfig do
80
113
  end
81
114
 
82
115
  context "when config/idp.yml exists" do
83
- before do
84
- allow(Rails).to receive(:env).and_return("environment")
85
- allow(Rails).to receive(:root).and_return("/railsroot")
86
- allow(File).to receive(:exists?).with("/railsroot/config/idp.yml").and_return(true)
87
- allow(File).to receive(:read).with("/railsroot/config/idp.yml").and_return(<<-IDP)
116
+ let(:idp_yaml) {
117
+ yaml = <<-IDP
88
118
  ---
89
119
  environment:
90
120
  assertion_consumer_logout_service_binding: assertion_consumer_logout_service_binding
@@ -104,8 +134,6 @@ environment:
104
134
  idp_cert_fingerprint: idp_cert_fingerprint
105
135
  idp_cert_fingerprint_algorithm: idp_cert_fingerprint_algorithm
106
136
  idp_entity_id: idp_entity_id
107
- idp_slo_target_url: idp_slo_target_url
108
- idp_sso_target_url: idp_sso_target_url
109
137
  issuer: issuer
110
138
  name_identifier_format: name_identifier_format
111
139
  name_identifier_value: name_identifier_value
@@ -116,6 +144,20 @@ environment:
116
144
  sessionindex: sessionindex
117
145
  sp_name_qualifier: sp_name_qualifier
118
146
  IDP
147
+ with_ruby_saml_1_12_or_greater(proc { yaml << <<SERVICE_URLS }, else_do: proc { yaml << <<TARGET_URLS })
148
+ idp_slo_service_url: idp_slo_service_url
149
+ idp_sso_service_url: idp_sso_service_url
150
+ SERVICE_URLS
151
+ idp_slo_target_url: idp_slo_service_url
152
+ idp_sso_target_url: idp_sso_service_url
153
+ TARGET_URLS
154
+ yaml
155
+ }
156
+ before do
157
+ allow(Rails).to receive(:env).and_return("environment")
158
+ allow(Rails).to receive(:root).and_return("/railsroot")
159
+ allow(File).to receive(:exists?).with("/railsroot/config/idp.yml").and_return(true)
160
+ allow(File).to receive(:read).with("/railsroot/config/idp.yml").and_return(idp_yaml)
119
161
  end
120
162
 
121
163
  it "uses that file's contents" do
@@ -136,8 +178,13 @@ environment:
136
178
  expect(saml_config.idp_cert_fingerprint).to eq('idp_cert_fingerprint')
137
179
  expect(saml_config.idp_cert_fingerprint_algorithm).to eq('idp_cert_fingerprint_algorithm')
138
180
  expect(saml_config.idp_entity_id).to eq('idp_entity_id')
139
- expect(saml_config.idp_slo_target_url).to eq('idp_slo_target_url')
140
- expect(saml_config.idp_sso_target_url).to eq('idp_sso_target_url')
181
+ with_ruby_saml_1_12_or_greater(proc {
182
+ expect(saml_config.idp_slo_service_url).to eq('idp_slo_service_url')
183
+ expect(saml_config.idp_sso_service_url).to eq('idp_sso_service_url')
184
+ }, else_do: proc {
185
+ expect(saml_config.idp_slo_target_url).to eq('idp_slo_service_url')
186
+ expect(saml_config.idp_sso_target_url).to eq('idp_sso_service_url')
187
+ })
141
188
  expect(saml_config.issuer).to eq('issuer')
142
189
  expect(saml_config.name_identifier_format).to eq('name_identifier_format')
143
190
  expect(saml_config.name_identifier_value).to eq('name_identifier_value')
@@ -1,6 +1,9 @@
1
1
  require 'rails_helper'
2
+ require 'support/ruby_saml_support'
2
3
 
3
4
  describe Devise::Strategies::SamlAuthenticatable do
5
+ include RubySamlSupport
6
+
4
7
  subject(:strategy) { described_class.new(env, :user) }
5
8
  let(:env) { {} }
6
9
  let(:errors) { ["Test1", "Test2"] }
@@ -16,7 +19,7 @@ describe Devise::Strategies::SamlAuthenticatable do
16
19
  let(:user) { double(:user) }
17
20
  before do
18
21
  allow(strategy).to receive(:mapping).and_return(mapping)
19
- allow(user).to receive(:after_saml_authentication)
22
+ allow(user).to(receive(:after_saml_authentication)) if user
20
23
  end
21
24
 
22
25
  let(:params) { {} }
@@ -54,17 +57,27 @@ describe Devise::Strategies::SamlAuthenticatable do
54
57
  let(:idp_providers_adapter) {
55
58
  Class.new {
56
59
  def self.settings(idp_entity_id)
57
- {
60
+ base = {
58
61
  assertion_consumer_service_url: "acs_url",
59
62
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
60
63
  name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
61
64
  issuer: "sp_issuer",
62
65
  idp_entity_id: "http://www.example.com",
63
66
  authn_context: "",
64
- idp_slo_target_url: "idp_slo_url",
65
- idp_sso_target_url: "http://idp_sso_url",
66
67
  idp_cert: "idp_cert"
67
68
  }
69
+ with_ruby_saml_1_12_or_greater(proc {
70
+ base.merge!(
71
+ idp_slo_service_url: "idp_slo_url",
72
+ idp_sso_service_url: "http://idp_sso_url",
73
+ )
74
+ }, else_do: proc {
75
+ base.merge!(
76
+ idp_slo_target_url: "idp_slo_url",
77
+ idp_sso_target_url: "http://idp_sso_url",
78
+ )
79
+ })
80
+ base
68
81
  end
69
82
  }
70
83
  }
@@ -93,8 +106,10 @@ describe Devise::Strategies::SamlAuthenticatable do
93
106
  let(:user) { nil }
94
107
 
95
108
  it "fails to authenticate" do
96
- expect(strategy).to receive(:fail!).with(:invalid)
97
109
  strategy.authenticate!
110
+ expect(strategy).to be_halted
111
+ expect(strategy.message).to be(:invalid)
112
+ expect(strategy.result).to be(:failure)
98
113
  end
99
114
 
100
115
  it 'logs the error' do
@@ -152,6 +167,40 @@ describe Devise::Strategies::SamlAuthenticatable do
152
167
  strategy.authenticate!
153
168
  end
154
169
  end
170
+
171
+ context "when saml_validate_in_response_to is opted-in to" do
172
+ let(:transaction_id) { "abc123" }
173
+
174
+ before do
175
+ allow(Devise).to receive(:saml_validate_in_response_to).and_return(true)
176
+ allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(session)
177
+ end
178
+
179
+ context "when the session has a saml_transaction_id" do
180
+ let(:session) { { saml_transaction_id: transaction_id }}
181
+
182
+ it "is valid with the matches_request_id parameter" do
183
+ expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: transaction_id))
184
+ expect(strategy).to be_valid
185
+ end
186
+
187
+ it "authenticates with the matches_request_id parameter" do
188
+ expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: transaction_id))
189
+
190
+ expect(strategy).to receive(:success!).with(user)
191
+ strategy.authenticate!
192
+ end
193
+ end
194
+
195
+ context "when the session is missing a saml_transaction_id" do
196
+ let(:session) { { } }
197
+
198
+ it "uses 'ID_MISSING' for matches_request_id so validation will fail" do
199
+ expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(matches_request_id: "ID_MISSING"))
200
+ strategy.authenticate!
201
+ end
202
+ end
203
+ end
155
204
  end
156
205
 
157
206
  it "is not valid without a SAMLResponse parameter" do
@@ -6,7 +6,7 @@ gemspec path: '../..'
6
6
  group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
- gem 'rails', '~> 5.2'
9
+ gem 'rails', '~> 5.2.0'
10
10
  gem 'rspec-rails', '~> 3.9'
11
11
  gem 'sqlite3', '~> 1.3.6'
12
12
  gem 'capybara'
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devise_saml_authenticatable.gemspec
4
+ gemspec path: '../..'
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec', '~> 3.0'
9
+ gem 'rails', '~> 6.0.0'
10
+ gem 'rspec-rails', '~> 5.0'
11
+ gem 'sqlite3', '~> 1.4.0'
12
+ gem 'capybara'
13
+ gem 'poltergeist'
14
+ end
@@ -1,17 +1,27 @@
1
1
  class IdpSettingsAdapter
2
2
  def self.settings(idp_entity_id)
3
3
  if idp_entity_id == "http://localhost:8020/saml/metadata"
4
- {
5
- assertion_consumer_service_url: "http://localhost:8020/users/saml/auth",
6
- assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
7
- name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
8
- issuer: "sp_issuer",
9
- idp_entity_id: "http://localhost:8020/saml/metadata",
10
- authn_context: "",
4
+ base = {
5
+ assertion_consumer_service_url: "http://localhost:8020/users/saml/auth",
6
+ assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
7
+ name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
8
+ issuer: "sp_issuer",
9
+ idp_entity_id: "http://localhost:8020/saml/metadata",
10
+ authn_context: "",
11
+ idp_cert_fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
12
+ }
13
+ if Gem::Version.new(OneLogin::RubySaml::VERSION) >= Gem::Version.new("1.12.0")
14
+ base.merge!(
15
+ idp_slo_service_url: "http://localhost:8010/saml/logout",
16
+ idp_sso_service_url: "http://localhost:8010/saml/auth",
17
+ )
18
+ else
19
+ base.merge!(
11
20
  idp_slo_target_url: "http://localhost:8010/saml/logout",
12
21
  idp_sso_target_url: "http://localhost:8010/saml/auth",
13
- idp_cert_fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
14
- }
22
+ )
23
+ end
24
+ base
15
25
  else
16
26
  {}
17
27
  end
@@ -21,12 +21,11 @@ def create_app(name, env = {})
21
21
  puts "[#{name}] Creating Rails app"
22
22
  rails_new_options = %w[-T -J -S --skip-spring --skip-listen --skip-bootsnap]
23
23
  rails_new_options << "-O" if name == "idp"
24
- with_clean_env do
25
- Dir.chdir(working_directory) do
26
- FileUtils.rm_rf(name)
27
- puts("rails _#{Rails.version}_ new #{name} #{rails_new_options.join(" ")} -m #{File.expand_path("../#{name}_template.rb", __FILE__)}")
28
- system(env, "rails", "_#{Rails.version}_", "new", name, *rails_new_options, "-m", File.expand_path("../#{name}_template.rb", __FILE__))
29
- end
24
+ env.merge!("RUBY_SAML_VERSION" => OneLogin::RubySaml::VERSION)
25
+ Dir.chdir(working_directory) do
26
+ FileUtils.rm_rf(name)
27
+ puts("[#{working_directory}] rails _#{Rails.version}_ new #{name} #{rails_new_options.join(" ")} -m #{File.expand_path("../#{name}_template.rb", __FILE__)}")
28
+ system(env, "rails", "_#{Rails.version}_", "new", name, *rails_new_options, "-m", File.expand_path("../#{name}_template.rb", __FILE__))
30
29
  end
31
30
  end
32
31
 
@@ -0,0 +1,10 @@
1
+ module RubySamlSupport
2
+ VERSION_1_12 = Gem::Version.new("1.12.0")
3
+ def with_ruby_saml_1_12_or_greater(body, args = {else_do: nil})
4
+ if Gem::Version.new(OneLogin::RubySaml::VERSION) >= VERSION_1_12
5
+ body.call
6
+ else
7
+ args[:else_do].call
8
+ end
9
+ end
10
+ end
@@ -8,13 +8,14 @@ use_subject_to_authenticate = ENV.fetch('USE_SUBJECT_TO_AUTHENTICATE')
8
8
  idp_settings_adapter = ENV.fetch('IDP_SETTINGS_ADAPTER', "nil")
9
9
  idp_entity_id_reader = ENV.fetch('IDP_ENTITY_ID_READER', '"DeviseSamlAuthenticatable::DefaultIdpEntityIdReader"')
10
10
  saml_failed_callback = ENV.fetch('SAML_FAILED_CALLBACK', "nil")
11
+ ruby_saml_version = ENV.fetch("RUBY_SAML_VERSION")
11
12
 
12
13
  if Rails::VERSION::MAJOR < 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR < 2)
13
14
  gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "8b5889df1fcf03f76c7d66da02d8776bcc85b06bed7d9c592f076d9c8a5455ee6d4beae45986c3c030b40208db5e612f2a6ef8283036a352e3fae83c5eda36be"'
14
15
  end
15
16
 
16
17
  gem 'devise_saml_authenticatable', path: File.expand_path("../../..", __FILE__)
17
- gem 'ruby-saml', OneLogin::RubySaml::VERSION
18
+ gem 'ruby-saml', ruby_saml_version
18
19
  gem 'thin'
19
20
 
20
21
  insert_into_file('Gemfile', after: /\z/) {
@@ -92,13 +93,24 @@ after_bundle do
92
93
  config.saml_configure do |settings|
93
94
  settings.assertion_consumer_service_url = "http://localhost:8020/users/saml/auth"
94
95
  settings.issuer = "http://localhost:8020/saml/metadata"
95
- settings.idp_slo_target_url = "http://localhost:8009/saml/logout"
96
- settings.idp_sso_target_url = "http://localhost:8009/saml/auth"
97
96
  settings.idp_cert_fingerprint = "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
98
97
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
99
98
  end
100
99
  end
101
100
  CONFIG
101
+ if Gem::Version.new(ruby_saml_version) >= Gem::Version.new("1.12.0")
102
+ gsub_file 'config/initializers/devise.rb', /^ config\.saml_configure do \|settings\|$/, <<CONFIG
103
+ config.saml_configure do |settings|
104
+ settings.idp_slo_service_url = "http://localhost:8009/saml/logout"
105
+ settings.idp_sso_service_url = "http://localhost:8009/saml/auth"
106
+ CONFIG
107
+ else
108
+ gsub_file 'config/initializers/devise.rb', /^ config\.saml_configure do \|settings\|$/, <<CONFIG
109
+ config.saml_configure do |settings|
110
+ settings.idp_slo_target_url = "http://localhost:8009/saml/logout"
111
+ settings.idp_sso_target_url = "http://localhost:8009/saml/auth"
112
+ CONFIG
113
+ end
102
114
 
103
115
  generate :controller, 'home', 'index'
104
116
  insert_into_file('app/controllers/home_controller.rb', after: "class HomeController < ApplicationController\n") {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_saml_authenticatable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josef Sauter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-04 00:00:00.000000000 Z
11
+ date: 2021-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -45,9 +45,9 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/workflows/ci.yml"
48
49
  - ".gitignore"
49
50
  - ".rspec"
50
- - ".travis.yml"
51
51
  - Gemfile
52
52
  - LICENSE
53
53
  - README.md
@@ -82,12 +82,14 @@ files:
82
82
  - spec/support/Gemfile.rails5
83
83
  - spec/support/Gemfile.rails5.1
84
84
  - spec/support/Gemfile.rails5.2
85
+ - spec/support/Gemfile.rails6
85
86
  - spec/support/attribute-map.yml
86
87
  - spec/support/attribute_map_resolver.rb.erb
87
88
  - spec/support/idp_settings_adapter.rb.erb
88
89
  - spec/support/idp_template.rb
89
90
  - spec/support/rails_app.rb
90
91
  - spec/support/response_encrypted_nameid.xml.base64
92
+ - spec/support/ruby_saml_support.rb
91
93
  - spec/support/saml_idp-saml_slo_post.html.erb
92
94
  - spec/support/saml_idp_controller.rb.erb
93
95
  - spec/support/sp_template.rb
@@ -110,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
112
  - !ruby/object:Gem::Version
111
113
  version: '0'
112
114
  requirements: []
113
- rubygems_version: 3.0.3
115
+ rubygems_version: 3.1.4
114
116
  signing_key:
115
117
  specification_version: 4
116
118
  summary: SAML Authentication for devise
@@ -130,12 +132,14 @@ test_files:
130
132
  - spec/support/Gemfile.rails5
131
133
  - spec/support/Gemfile.rails5.1
132
134
  - spec/support/Gemfile.rails5.2
135
+ - spec/support/Gemfile.rails6
133
136
  - spec/support/attribute-map.yml
134
137
  - spec/support/attribute_map_resolver.rb.erb
135
138
  - spec/support/idp_settings_adapter.rb.erb
136
139
  - spec/support/idp_template.rb
137
140
  - spec/support/rails_app.rb
138
141
  - spec/support/response_encrypted_nameid.xml.base64
142
+ - spec/support/ruby_saml_support.rb
139
143
  - spec/support/saml_idp-saml_slo_post.html.erb
140
144
  - spec/support/saml_idp_controller.rb.erb
141
145
  - spec/support/sp_template.rb
data/.travis.yml DELETED
@@ -1,52 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - "2.0.0"
4
- - "2.1.10"
5
- - "2.2.10"
6
- - "2.3.8"
7
- - "2.4.10"
8
- - "2.5.8"
9
- - "2.6.6"
10
- - "2.7.1"
11
- gemfile:
12
- - Gemfile
13
- - spec/support/Gemfile.rails5.2
14
- - spec/support/Gemfile.rails5.1
15
- - spec/support/Gemfile.rails5
16
- - spec/support/Gemfile.rails4
17
- matrix:
18
- allow_failures:
19
- - rvm: "2.0.0"
20
- gemfile: Gemfile
21
- - rvm: "2.0.0"
22
- gemfile: spec/support/Gemfile.rails5
23
- - rvm: "2.0.0"
24
- gemfile: spec/support/Gemfile.rails5.1
25
- - rvm: "2.0.0"
26
- gemfile: spec/support/Gemfile.rails5.2
27
- - rvm: "2.1.10"
28
- gemfile: Gemfile
29
- - rvm: "2.1.10"
30
- gemfile: spec/support/Gemfile.rails5
31
- - rvm: "2.1.10"
32
- gemfile: spec/support/Gemfile.rails5.1
33
- - rvm: "2.1.10"
34
- gemfile: spec/support/Gemfile.rails5.2
35
- - rvm: "2.2.10"
36
- gemfile: Gemfile
37
- - rvm: "2.2.10"
38
- gemfile: spec/support/Gemfile.rails5.2
39
- - rvm: "2.3.8"
40
- gemfile: Gemfile
41
- - rvm: "2.4.10"
42
- gemfile: Gemfile
43
- - rvm: "2.6.6"
44
- gemfile: spec/support/Gemfile.rails4
45
- - rvm: "2.7.1"
46
- gemfile: spec/support/Gemfile.rails4
47
-
48
- before_install:
49
- - command -v bundle || gem install bundler -v '~> 1.17.3'
50
-
51
- script:
52
- - bundle exec rake