devise_saml_authenticatable 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 811f4c9c0306a1c5ba28080ac773ba88022b79cc
4
- data.tar.gz: f2571561eb9c3ba05832e65d1b8ab15d3c9c408c
3
+ metadata.gz: eeabad37a5473557499db0b8de604afb7740608d
4
+ data.tar.gz: 952108992254613cdf27f72aedbf6e2a772f95a3
5
5
  SHA512:
6
- metadata.gz: c21a2ac40153fcb51c843fcdfa081ba2caab761c94a43cf1ebe297d1260021fc54409fe6cdac9cc3c6340db077ba3c8d2cb15e2f83a9af8886cff90ac128cb58
7
- data.tar.gz: dc0c2d6f2174e0f5bef4a6b4590ed90558741a8a86af2754bb5c1094d039c29cd9da726f804bbd78a1693023ac4f50ad81d095956ad728272144d4977bb25eec
6
+ metadata.gz: 720d7b21c40596b762a85c5c0df2f9dc3d1af7fdce86a7c8562428b866e79eab6a768e070c648fd4c657415bca175102d4eaa0dff6866c7cc8b14025d1bf5101
7
+ data.tar.gz: c3368984264f54a99a37ce9e4b546b7bbd2460a834bc57300dfaf242232619bad50f51bb66adc4d2e1e41d287ce4f3f7f85923697835a06bf14f73968a31f1ed
data/.gitignore CHANGED
@@ -4,7 +4,7 @@
4
4
  .config
5
5
  .idea/
6
6
  .yardoc
7
- Gemfile.lock
7
+ Gemfile*.lock
8
8
  InstalledFiles
9
9
  _yardoc
10
10
  coverage
@@ -2,11 +2,30 @@ language: ruby
2
2
  rvm:
3
3
  - "1.9.3"
4
4
  - "2.0.0"
5
- - "2.1.0"
6
- - "2.2.0"
5
+ - "2.1.9"
6
+ - "2.2.5"
7
+ - "2.3.1"
8
+ gemfile:
9
+ - Gemfile
10
+ - spec/support/Gemfile.rails4
11
+ - spec/support/Gemfile.ruby-saml-1.3
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: "1.9.3"
15
+ gemfile: Gemfile
16
+ - rvm: "1.9.3"
17
+ gemfile: spec/support/Gemfile.ruby-saml-1.3
18
+ - rvm: "2.0.0"
19
+ gemfile: Gemfile
20
+ - rvm: "2.0.0"
21
+ gemfile: spec/support/Gemfile.ruby-saml-1.3
22
+ - rvm: "2.1.9"
23
+ gemfile: Gemfile
24
+ - rvm: "2.1.9"
25
+ gemfile: spec/support/Gemfile.ruby-saml-1.3
7
26
 
8
27
  script:
9
- - xvfb-run bundle exec rake
28
+ - bundle exec rake
10
29
 
11
30
  notifications:
12
31
  hipchat:
@@ -15,4 +34,4 @@ notifications:
15
34
  template:
16
35
  - '%{repository}<a href="%{build_url}">#%{build_number}</a> (%{branch} - <a href="%{compare_url}">%{commit}</a> : %{author}): %{message}'
17
36
  format: html
18
- on_pull_requests: false
37
+ on_pull_requests: true
data/Gemfile CHANGED
@@ -6,10 +6,11 @@ gemspec
6
6
  group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
- gem 'rails', '~> 4.0'
9
+ gem 'rails', '~> 5.0'
10
10
  gem 'rspec-rails'
11
11
  gem 'sqlite3'
12
- gem 'capybara-webkit'
12
+ gem 'capybara'
13
+ gem 'poltergeist'
13
14
 
14
15
  # Lock down versions of gems for older versions of Ruby
15
16
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
data/README.md CHANGED
@@ -160,6 +160,7 @@ end
160
160
  Detecting the entity ID passed to the `settings` method is done by `config.idp_entity_id_reader`.
161
161
  By default this will find the `Issuer` in the SAML request.
162
162
  You can support more use cases by writing your own and implementing the `.entity_id` method.
163
+ If you use encrypted assertions, your entity ID reader will need to understand how to decrypt the response from each of the possible IdPs.
163
164
 
164
165
  ## Identity Provider
165
166
 
@@ -3,12 +3,13 @@ require "ruby-saml"
3
3
  class Devise::SamlSessionsController < Devise::SessionsController
4
4
  include DeviseSamlAuthenticatable::SamlConfig
5
5
  unloadable if Rails::VERSION::MAJOR < 4
6
- skip_before_filter :verify_authenticity_token
6
+ skip_before_filter :verify_authenticity_token, raise: false
7
7
 
8
8
  def new
9
9
  idp_entity_id = get_idp_entity_id(params)
10
10
  request = OneLogin::RubySaml::Authrequest.new
11
- action = request.create(saml_config(idp_entity_id))
11
+ auth_params = { RelayState: relay_state } if relay_state
12
+ action = request.create(saml_config(idp_entity_id), auth_params || {})
12
13
  redirect_to action
13
14
  end
14
15
 
@@ -40,6 +41,12 @@ class Devise::SamlSessionsController < Devise::SessionsController
40
41
 
41
42
  protected
42
43
 
44
+ def relay_state
45
+ @relay_state ||= if Devise.saml_relay_state.present?
46
+ Devise.saml_relay_state.call(request)
47
+ end
48
+ end
49
+
43
50
  # Override devise to send user to IdP logout for SLO
44
51
  def after_sign_out_path_for(_)
45
52
  request = OneLogin::RubySaml::Logoutrequest.new
@@ -57,6 +57,11 @@ module Devise
57
57
  mattr_accessor :saml_failed_callback
58
58
  @@saml_failed_callback
59
59
 
60
+ # lambda that generates the RelayState param for the SAML AuthRequest, takes request
61
+ # from SamlSessionsController#new action as an argument
62
+ mattr_accessor :saml_relay_state
63
+ @@saml_relay_state
64
+
60
65
  mattr_accessor :saml_config
61
66
  @@saml_config = OneLogin::RubySaml::Settings.new
62
67
  def self.saml_configure
@@ -28,7 +28,7 @@ module Devise
28
28
  end
29
29
 
30
30
  module ClassMethods
31
- def authenticate_with_saml(saml_response)
31
+ def authenticate_with_saml(saml_response, relay_state)
32
32
  key = Devise.saml_default_user_key
33
33
  attributes = saml_response.attributes
34
34
  if (Devise.saml_use_subject)
@@ -36,8 +36,9 @@ module Devise
36
36
  else
37
37
  inv_attr = attribute_map.invert
38
38
  auth_value = attributes[inv_attr[key.to_s]]
39
- auth_value.try(:downcase!) if Devise.case_insensitive_keys.include?(key)
40
39
  end
40
+ auth_value.try(:downcase!) if Devise.case_insensitive_keys.include?(key)
41
+
41
42
  resource = where(key => auth_value).first
42
43
 
43
44
  if resource.nil?
@@ -13,14 +13,11 @@ module Devise
13
13
  end
14
14
 
15
15
  def authenticate!
16
- @response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_config(get_idp_entity_id(params)))
17
- resource = mapping.to.authenticate_with_saml(@response)
18
- if @response.is_valid? && resource
19
- resource.after_saml_authentication(@response.sessionindex)
20
- success!(resource)
21
- else
22
- fail!(:invalid)
23
- Devise.saml_failed_callback.new.handle(@response, self) if Devise.saml_failed_callback
16
+ parse_saml_response
17
+ retrieve_resource unless self.halted?
18
+ unless self.halted?
19
+ @resource.after_saml_authentication(@response.sessionindex)
20
+ success!(@resource)
24
21
  end
25
22
  end
26
23
 
@@ -28,7 +25,28 @@ module Devise
28
25
  # Any known way on how to let the IdP send the CSRF token along with the SAMLResponse ?
29
26
  # Please let me know!
30
27
  def store?
31
- true
28
+ !mapping.to.skip_session_storage.include?(:saml_auth)
29
+ end
30
+
31
+ private
32
+ def parse_saml_response
33
+ @response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_config(get_idp_entity_id(params)))
34
+ unless @response.is_valid?
35
+ failed_auth("Auth errors: #{@response.errors.join(', ')}")
36
+ end
37
+ end
38
+
39
+ def retrieve_resource
40
+ @resource = mapping.to.authenticate_with_saml(@response, params[:RelayState])
41
+ if @resource.nil?
42
+ failed_auth("Resource could not be found")
43
+ end
44
+ end
45
+
46
+ def failed_auth(msg)
47
+ DeviseSamlAuthenticatable::Logger.send(msg)
48
+ fail!(:invalid)
49
+ Devise.saml_failed_callback.new.handle(@response, self) if Devise.saml_failed_callback
32
50
  end
33
51
 
34
52
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseSamlAuthenticatable
2
- VERSION = "1.3.0"
2
+ VERSION = "1.3.1"
3
3
  end
@@ -38,9 +38,17 @@ describe Devise::SamlSessionsController, type: :controller do
38
38
  describe '#new' do
39
39
  let(:saml_response) { File.read(File.join(File.dirname(__FILE__), '../../support', 'response_encrypted_nameid.xml.base64')) }
40
40
 
41
+ subject(:do_get) {
42
+ if Rails::VERSION::MAJOR > 4
43
+ get :new, params: {"SAMLResponse" => saml_response}
44
+ else
45
+ get :new, "SAMLResponse" => saml_response
46
+ end
47
+ }
48
+
41
49
  context "when using the default saml config" do
42
50
  it "redirects to the IdP SSO target url" do
43
- get :new, "SAMLResponse" => saml_response
51
+ do_get
44
52
  expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/auth\?SAMLRequest=))
45
53
  end
46
54
  end
@@ -51,13 +59,23 @@ describe Devise::SamlSessionsController, type: :controller do
51
59
  end
52
60
 
53
61
  it "redirects to the associated IdP SSO target url" do
54
- get :new, "SAMLResponse" => saml_response
62
+ do_get
55
63
  expect(response).to redirect_to(%r(\Ahttp://idp_sso_url\?SAMLRequest=))
56
64
  end
57
65
 
58
66
  it "uses the DefaultIdpEntityIdReader" do
59
67
  expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
60
- get :new, "SAMLResponse" => saml_response
68
+ do_get
69
+ end
70
+
71
+ context "with a relay_state lambda defined" do
72
+ let(:relay_state) { ->(request) { "123" } }
73
+
74
+ it "includes the RelayState param in the request to the IdP" do
75
+ expect(Devise).to receive(:saml_relay_state).at_least(:once).and_return(relay_state)
76
+ do_get
77
+ expect(response).to redirect_to(%r(\Ahttp://idp_sso_url\?SAMLRequest=.*&RelayState=123))
78
+ end
61
79
  end
62
80
 
63
81
  context "with a specified idp entity id reader" do
@@ -67,6 +85,14 @@ describe Devise::SamlSessionsController, type: :controller do
67
85
  end
68
86
  end
69
87
 
88
+ subject(:do_get) {
89
+ if Rails::VERSION::MAJOR > 4
90
+ get :new, params: {entity_id: "http://www.example.com"}
91
+ else
92
+ get :new, entity_id: "http://www.example.com"
93
+ end
94
+ }
95
+
70
96
  before do
71
97
  @default_reader = Devise.idp_entity_id_reader
72
98
  Devise.idp_entity_id_reader = OurIdpEntityIdReader # which will have some different behavior
@@ -77,7 +103,7 @@ describe Devise::SamlSessionsController, type: :controller do
77
103
  end
78
104
 
79
105
  it "redirects to the associated IdP SSO target url" do
80
- get :new, entity_id: "http://www.example.com"
106
+ do_get
81
107
  expect(response).to redirect_to(%r(\Ahttp://idp_sso_url\?SAMLRequest=))
82
108
  end
83
109
  end
@@ -129,76 +155,98 @@ describe Devise::SamlSessionsController, type: :controller do
129
155
  end
130
156
 
131
157
  describe '#idp_sign_out' do
132
- let(:name_id) { '12312312' }
133
- let(:saml_request) { double(:slo_logoutrequest, {
134
- id: 42,
135
- name_id: name_id,
136
- issuer: "http://www.example.com"
137
- }) }
138
158
  let(:saml_response) { double(:slo_logoutresponse) }
139
159
  let(:response_url) { 'http://localhost/logout_response' }
140
-
141
160
  before do
142
- allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
143
161
  allow(OneLogin::RubySaml::SloLogoutresponse).to receive(:new).and_return(saml_response)
144
162
  allow(saml_response).to receive(:create).and_return(response_url)
145
163
  end
146
164
 
147
- it 'returns invalid request if SAMLRequest is not passed' do
148
- expect(User).not_to receive(:reset_session_key_for).with(name_id)
165
+ it 'returns invalid request if SAMLRequest or SAMLResponse is not passed' do
166
+ expect(User).not_to receive(:reset_session_key_for)
149
167
  post :idp_sign_out
150
168
  expect(response.status).to eq 500
151
169
  end
152
170
 
153
- it 'accepts a LogoutResponse and redirects sign_in' do
154
- post :idp_sign_out, SAMLResponse: 'stubbed_response'
155
- expect(response.status).to eq 302
156
- expect(response).to redirect_to '/users/saml/sign_in'
157
- end
171
+ context "when receiving a logout response from the IdP after redirecting an SP logout request" do
172
+ subject(:do_post) {
173
+ if Rails::VERSION::MAJOR > 4
174
+ post :idp_sign_out, params: {SAMLResponse: "stubbed_response"}
175
+ else
176
+ post :idp_sign_out, SAMLResponse: "stubbed_response"
177
+ end
178
+ }
158
179
 
159
- context "with a specified idp" do
160
- let(:idp_entity_id) { "http://www.example.com" }
161
- before do
162
- Devise.idp_settings_adapter = idp_providers_adapter
180
+ it 'accepts a LogoutResponse and redirects sign_in' do
181
+ do_post
182
+ expect(response.status).to eq 302
183
+ expect(response).to redirect_to '/users/saml/sign_in'
163
184
  end
164
185
 
165
- it "accepts a LogoutResponse for the associated slo_target_url and redirects to sign_in" do
166
- post :idp_sign_out, SAMLRequest: "stubbed_logout_request"
167
- expect(response.status).to eq 302
168
- expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id)
169
- expect(response).to redirect_to "http://localhost/logout_response"
186
+ context 'when saml_sign_out_success_url is configured' do
187
+ let(:test_url) { '/test/url' }
188
+ before do
189
+ Devise.saml_sign_out_success_url = test_url
190
+ end
191
+
192
+ it 'accepts a LogoutResponse and returns success' do
193
+ do_post
194
+ expect(response.status).to eq 302
195
+ expect(response).to redirect_to test_url
196
+ end
170
197
  end
171
198
  end
172
199
 
173
- context 'when saml_sign_out_success_url is configured' do
174
- let(:test_url) { '/test/url' }
200
+ context "when receiving an IdP logout request" do
201
+ subject(:do_post) {
202
+ if Rails::VERSION::MAJOR > 4
203
+ post :idp_sign_out, params: {SAMLRequest: "stubbed_logout_request"}
204
+ else
205
+ post :idp_sign_out, SAMLRequest: "stubbed_logout_request"
206
+ end
207
+ }
208
+
209
+ let(:saml_request) { double(:slo_logoutrequest, {
210
+ id: 42,
211
+ name_id: name_id,
212
+ issuer: "http://www.example.com"
213
+ }) }
214
+ let(:name_id) { '12312312' }
175
215
  before do
176
- Devise.saml_sign_out_success_url = test_url
216
+ allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
177
217
  end
178
218
 
179
- it 'accepts a LogoutResponse and returns success' do
180
- post :idp_sign_out, SAMLResponse: 'stubbed_response'
181
- expect(response.status).to eq 302
182
- expect(response).to redirect_to test_url
219
+ it 'direct the resource to reset the session key' do
220
+ expect(User).to receive(:reset_session_key_for).with(name_id)
221
+ do_post
222
+ expect(response).to redirect_to response_url
183
223
  end
184
- end
185
224
 
186
- context 'when saml_session_index_key is not configured' do
187
- before do
188
- Devise.saml_session_index_key = nil
189
- end
225
+ context "with a specified idp" do
226
+ let(:idp_entity_id) { "http://www.example.com" }
227
+ before do
228
+ Devise.idp_settings_adapter = idp_providers_adapter
229
+ end
190
230
 
191
- it 'returns invalid request' do
192
- expect(User).not_to receive(:reset_session_key_for).with(name_id)
193
- post :idp_sign_out, SAMLRequest: 'stubbed_request'
194
- expect(response.status).to eq 500
231
+ it "accepts a LogoutResponse for the associated slo_target_url and redirects to sign_in" do
232
+ do_post
233
+ expect(response.status).to eq 302
234
+ expect(idp_providers_adapter).to have_received(:settings).with(idp_entity_id)
235
+ expect(response).to redirect_to "http://localhost/logout_response"
236
+ end
195
237
  end
196
- end
197
238
 
198
- it 'direct the resource to reset the session key' do
199
- expect(User).to receive(:reset_session_key_for).with(name_id)
200
- post :idp_sign_out, SAMLRequest: 'stubbed_request'
201
- expect(response).to redirect_to response_url
239
+ context 'when saml_session_index_key is not configured' do
240
+ before do
241
+ Devise.saml_session_index_key = nil
242
+ end
243
+
244
+ it 'returns invalid request' do
245
+ expect(User).not_to receive(:reset_session_key_for).with(name_id)
246
+ do_post
247
+ expect(response.status).to eq 500
248
+ end
249
+ end
202
250
  end
203
251
  end
204
252
  end
@@ -58,12 +58,12 @@ describe Devise::Models::SamlAuthenticatable do
58
58
  it "looks up the user by the configured default user key" do
59
59
  user = Model.new(new_record: false)
60
60
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
61
- expect(Model.authenticate_with_saml(response)).to eq(user)
61
+ expect(Model.authenticate_with_saml(response, nil)).to eq(user)
62
62
  end
63
63
 
64
64
  it "returns nil if it cannot find a user" do
65
65
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
66
- expect(Model.authenticate_with_saml(response)).to be_nil
66
+ expect(Model.authenticate_with_saml(response, nil)).to be_nil
67
67
  end
68
68
 
69
69
  context "when configured to use the subject" do
@@ -77,12 +77,12 @@ describe Devise::Models::SamlAuthenticatable do
77
77
  it "looks up the user by the configured default user key" do
78
78
  user = Model.new(new_record: false)
79
79
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
80
- expect(Model.authenticate_with_saml(response)).to eq(user)
80
+ expect(Model.authenticate_with_saml(response, nil)).to eq(user)
81
81
  end
82
82
 
83
83
  it "returns nil if it cannot find a user" do
84
84
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
85
- expect(Model.authenticate_with_saml(response)).to be_nil
85
+ expect(Model.authenticate_with_saml(response, nil)).to be_nil
86
86
  end
87
87
 
88
88
  context "when configured to create a user and the user is not found" do
@@ -92,7 +92,7 @@ describe Devise::Models::SamlAuthenticatable do
92
92
 
93
93
  it "creates and returns a new user with the name identifier and given attributes" do
94
94
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
95
- model = Model.authenticate_with_saml(response)
95
+ model = Model.authenticate_with_saml(response, nil)
96
96
  expect(model.email).to eq('user@example.com')
97
97
  expect(model.name).to eq('A User')
98
98
  expect(model.saved).to be(true)
@@ -107,7 +107,7 @@ describe Devise::Models::SamlAuthenticatable do
107
107
  it "creates and returns a new user with the name identifier and given attributes" do
108
108
  user = Model.new(email: "old_mail@mail.com", name: "old name", new_record: false)
109
109
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
110
- model = Model.authenticate_with_saml(response)
110
+ model = Model.authenticate_with_saml(response, nil)
111
111
  expect(model.email).to eq('user@example.com')
112
112
  expect(model.name).to eq('A User')
113
113
  expect(model.saved).to be(true)
@@ -122,7 +122,7 @@ describe Devise::Models::SamlAuthenticatable do
122
122
 
123
123
  it "creates and returns a new user with the given attributes" do
124
124
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
125
- model = Model.authenticate_with_saml(response)
125
+ model = Model.authenticate_with_saml(response, nil)
126
126
  expect(model.email).to eq('user@example.com')
127
127
  expect(model.name).to eq('A User')
128
128
  expect(model.saved).to be(true)
@@ -136,29 +136,46 @@ describe Devise::Models::SamlAuthenticatable do
136
136
 
137
137
  it "returns nil if the user is not found" do
138
138
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([])
139
- expect(Model.authenticate_with_saml(response)).to be_nil
139
+ expect(Model.authenticate_with_saml(response, nil)).to be_nil
140
140
  end
141
141
 
142
142
  it "updates the attributes if the user is found" do
143
143
  user = Model.new(email: "old_mail@mail.com", name: "old name", new_record: false)
144
144
  expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
145
- model = Model.authenticate_with_saml(response)
145
+ model = Model.authenticate_with_saml(response, nil)
146
146
  expect(model.email).to eq('user@example.com')
147
147
  expect(model.name).to eq('A User')
148
148
  expect(model.saved).to be(true)
149
149
  end
150
150
  end
151
151
 
152
-
153
152
  context "when configured with a case-insensitive key" do
154
- before do
155
- allow(Devise).to receive(:case_insensitive_keys).and_return([:email])
153
+ shared_examples "correct downcasing" do
154
+ before do
155
+ allow(Devise).to receive(:case_insensitive_keys).and_return([:email])
156
+ end
157
+
158
+ it "looks up the user with a downcased value" do
159
+ user = Model.new(new_record: false)
160
+ expect(Model).to receive(:where).with(email: 'upper@example.com').and_return([user])
161
+ expect(Model.authenticate_with_saml(response, nil)).to eq(user)
162
+ end
156
163
  end
157
164
 
158
- it "looks up the user with a downcased value" do
159
- user = Model.new(new_record: false)
160
- expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
161
- expect(Model.authenticate_with_saml(response)).to eq(user)
165
+ context "when configured to use the subject" do
166
+ let(:name_id) { 'UPPER@example.com' }
167
+
168
+ before do
169
+ allow(Devise).to receive(:saml_use_subject).and_return(true)
170
+ end
171
+
172
+ include_examples "correct downcasing"
173
+ end
174
+
175
+ context "when using default user key" do
176
+ let(:attributes) { OneLogin::RubySaml::Attributes.new('saml-email-format' => ['UPPER@example.com']) }
177
+
178
+ include_examples "correct downcasing"
162
179
  end
163
180
  end
164
181
  end
@@ -3,15 +3,16 @@ require 'rails_helper'
3
3
  describe Devise::Strategies::SamlAuthenticatable do
4
4
  subject(:strategy) { described_class.new(env, :user) }
5
5
  let(:env) { {} }
6
+ let(:errors) { ["Test1", "Test2"] }
6
7
 
7
- let(:response) { double(:response, issuers: [idp_entity_id], :settings= => nil, is_valid?: true, sessionindex: '123123123') }
8
+ let(:response) { double(:response, issuers: [idp_entity_id], :settings= => nil, is_valid?: true, sessionindex: '123123123', errors: errors) }
8
9
  let(:idp_entity_id) { "https://test/saml/metadata/123123" }
9
10
  before do
10
11
  allow(OneLogin::RubySaml::Response).to receive(:new).and_return(response)
11
12
  end
12
13
 
13
14
  let(:mapping) { double(:mapping, to: user_class) }
14
- let(:user_class) { double(:user_class, authenticate_with_saml: user) }
15
+ let(:user_class) { double(:user_class, authenticate_with_saml: user, skip_session_storage: []) }
15
16
  let(:user) { double(:user) }
16
17
  before do
17
18
  allow(strategy).to receive(:mapping).and_return(mapping)
@@ -32,13 +33,23 @@ describe Devise::Strategies::SamlAuthenticatable do
32
33
 
33
34
  it "authenticates with the response" do
34
35
  expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
35
- expect(user_class).to receive(:authenticate_with_saml).with(response)
36
+ expect(user_class).to receive(:authenticate_with_saml).with(response, nil)
36
37
  expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
37
38
 
38
39
  expect(strategy).to receive(:success!).with(user)
39
40
  strategy.authenticate!
40
41
  end
41
42
 
43
+ context "and a RelayState parameter" do
44
+ let(:params) { super().merge(RelayState: "foo") }
45
+ it "authenticates with the response" do
46
+ expect(user_class).to receive(:authenticate_with_saml).with(response, params[:RelayState])
47
+
48
+ expect(strategy).to receive(:success!).with(user)
49
+ strategy.authenticate!
50
+ end
51
+ end
52
+
42
53
  context "when saml config uses an idp_adapter" do
43
54
  let(:idp_providers_adapter) {
44
55
  Class.new {
@@ -70,7 +81,7 @@ describe Devise::Strategies::SamlAuthenticatable do
70
81
  it "authenticates with the response for the corresponding idp" do
71
82
  expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], anything)
72
83
  expect(idp_providers_adapter).to receive(:settings).with(idp_entity_id)
73
- expect(user_class).to receive(:authenticate_with_saml).with(response)
84
+ expect(user_class).to receive(:authenticate_with_saml).with(response, params[:RelayState])
74
85
  expect(user).to receive(:after_saml_authentication).with(response.sessionindex)
75
86
 
76
87
  expect(strategy).to receive(:success!).with(user)
@@ -85,6 +96,11 @@ describe Devise::Strategies::SamlAuthenticatable do
85
96
  expect(strategy).to receive(:fail!).with(:invalid)
86
97
  strategy.authenticate!
87
98
  end
99
+
100
+ it 'logs the error' do
101
+ expect(DeviseSamlAuthenticatable::Logger).to receive(:send).with('Resource could not be found')
102
+ strategy.authenticate!
103
+ end
88
104
  end
89
105
 
90
106
  context "and the SAML response is not valid" do
@@ -103,6 +119,11 @@ describe Devise::Strategies::SamlAuthenticatable do
103
119
  Devise.saml_failed_callback = @saml_failed_login
104
120
  end
105
121
 
122
+ it 'logs the error' do
123
+ expect(DeviseSamlAuthenticatable::Logger).to receive(:send).with('Auth errors: Test1, Test2')
124
+ strategy.authenticate!
125
+ end
126
+
106
127
  it "fails to authenticate" do
107
128
  expect(strategy).to receive(:fail!).with(:invalid)
108
129
  strategy.authenticate!
@@ -122,4 +143,14 @@ describe Devise::Strategies::SamlAuthenticatable do
122
143
  it "is permanent" do
123
144
  expect(strategy).to be_store
124
145
  end
146
+
147
+ context "when the user should not be stored in the session" do
148
+ before do
149
+ allow(user_class).to receive(:skip_session_storage).and_return([:saml_auth])
150
+ end
151
+
152
+ it "is not stored" do
153
+ expect(strategy).not_to be_store
154
+ end
155
+ end
125
156
  end
@@ -3,8 +3,8 @@ require 'net/http'
3
3
  require 'timeout'
4
4
  require 'uri'
5
5
  require 'capybara/rspec'
6
- require 'capybara/webkit'
7
- Capybara.default_driver = :webkit
6
+ require 'capybara/poltergeist'
7
+ Capybara.default_driver = :poltergeist
8
8
 
9
9
  describe "SAML Authentication", type: :feature do
10
10
  let(:idp_port) { 8009 }
@@ -0,0 +1,23 @@
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', '~> 4.0'
10
+ gem 'rspec-rails'
11
+ gem 'sqlite3'
12
+ gem 'capybara'
13
+ gem 'poltergeist'
14
+
15
+ # Lock down versions of gems for older versions of Ruby
16
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
17
+ gem 'addressable', '~> 2.4.0'
18
+ gem 'mime-types', '~> 2.99'
19
+ end
20
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
21
+ gem 'devise', '~> 3.5'
22
+ end
23
+ end
@@ -0,0 +1,23 @@
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', '~> 5.0'
10
+ gem 'rspec-rails'
11
+ gem 'ruby-saml', '~> 1.3.0'
12
+ gem 'sqlite3'
13
+ gem 'capybara'
14
+ gem 'poltergeist'
15
+
16
+ # Lock down versions of gems for older versions of Ruby
17
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
18
+ gem 'mime-types', '~> 2.99'
19
+ end
20
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
21
+ gem 'devise', '~> 3.5'
22
+ end
23
+ end
@@ -9,9 +9,6 @@ gem 'thin'
9
9
  insert_into_file('Gemfile', after: /\z/) {
10
10
  <<-GEMFILE
11
11
  # Lock down versions of gems for older versions of Ruby
12
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
13
- gem 'mime-types', '~> 2.99'
14
- end
15
12
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
16
13
  gem 'devise', '~> 3.5'
17
14
  end
@@ -12,13 +12,11 @@ def app_ready?(pid, port)
12
12
  end
13
13
 
14
14
  def create_app(name, env = {})
15
- rails_new_options = %w(-T -J -S --skip-spring)
15
+ rails_new_options = %w(-T -J -S --skip-spring --skip-listen)
16
16
  rails_new_options << "-O" if name == 'idp'
17
- Bundler.with_clean_env do
18
- Dir.chdir(File.expand_path('../../support', __FILE__)) do
19
- FileUtils.rm_rf(name)
20
- system(env, "rails", "new", name, *rails_new_options, "-m", "#{name}_template.rb")
21
- end
17
+ Dir.chdir(File.expand_path('../../support', __FILE__)) do
18
+ FileUtils.rm_rf(name)
19
+ system(env, "rails", "new", name, *rails_new_options, "-m", "#{name}_template.rb")
22
20
  end
23
21
  end
24
22
 
@@ -1,5 +1,7 @@
1
1
  # Set up a SAML Service Provider
2
2
 
3
+ require "onelogin/ruby-saml/version"
4
+
3
5
  saml_session_index_key = ENV.fetch('SAML_SESSION_INDEX_KEY', ":session_index")
4
6
  use_subject_to_authenticate = ENV.fetch('USE_SUBJECT_TO_AUTHENTICATE')
5
7
  idp_settings_adapter = ENV.fetch('IDP_SETTINGS_ADAPTER', "nil")
@@ -7,14 +9,12 @@ idp_entity_id_reader = ENV.fetch('IDP_ENTITY_ID_READER', "DeviseSamlAuthenticata
7
9
  saml_failed_callback = ENV.fetch('SAML_FAILED_CALLBACK', "nil")
8
10
 
9
11
  gem 'devise_saml_authenticatable', path: '../../..'
12
+ gem 'ruby-saml', OneLogin::RubySaml::VERSION
10
13
  gem 'thin'
11
14
 
12
15
  insert_into_file('Gemfile', after: /\z/) {
13
16
  <<-GEMFILE
14
17
  # Lock down versions of gems for older versions of Ruby
15
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
16
- gem 'mime-types', '~> 2.99'
17
- end
18
18
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
19
19
  gem 'devise', '~> 3.5'
20
20
  end
@@ -100,7 +100,7 @@ end
100
100
  route "resources :users, only: [:create]"
101
101
  create_file('app/controllers/users_controller.rb', <<-USERS)
102
102
  class UsersController < ApplicationController
103
- skip_before_filter :verify_authenticity_token
103
+ skip_before_action :verify_authenticity_token
104
104
  def create
105
105
  User.create!(email: params[:email])
106
106
  render nothing: true, status: 201
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.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josef Sauter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-07 00:00:00.000000000 Z
11
+ date: 2016-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -72,6 +72,8 @@ files:
72
72
  - spec/features/saml_authentication_spec.rb
73
73
  - spec/rails_helper.rb
74
74
  - spec/spec_helper.rb
75
+ - spec/support/Gemfile.rails4
76
+ - spec/support/Gemfile.ruby-saml-1.3
75
77
  - spec/support/idp_settings_adapter.rb.erb
76
78
  - spec/support/idp_template.rb
77
79
  - spec/support/rails_app.rb
@@ -111,6 +113,8 @@ test_files:
111
113
  - spec/features/saml_authentication_spec.rb
112
114
  - spec/rails_helper.rb
113
115
  - spec/spec_helper.rb
116
+ - spec/support/Gemfile.rails4
117
+ - spec/support/Gemfile.ruby-saml-1.3
114
118
  - spec/support/idp_settings_adapter.rb.erb
115
119
  - spec/support/idp_template.rb
116
120
  - spec/support/rails_app.rb