devise_saml_authenticatable 1.6.3 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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