devise_saml_authenticatable 1.3.1 → 1.6.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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -2
  3. data/.travis.yml +37 -22
  4. data/Gemfile +2 -10
  5. data/README.md +127 -44
  6. data/app/controllers/devise/saml_sessions_controller.rb +38 -7
  7. data/devise_saml_authenticatable.gemspec +2 -1
  8. data/lib/devise_saml_authenticatable.rb +70 -0
  9. data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
  10. data/lib/devise_saml_authenticatable/default_idp_entity_id_reader.rb +10 -2
  11. data/lib/devise_saml_authenticatable/exception.rb +1 -1
  12. data/lib/devise_saml_authenticatable/model.rb +20 -32
  13. data/lib/devise_saml_authenticatable/routes.rb +17 -6
  14. data/lib/devise_saml_authenticatable/saml_mapped_attributes.rb +38 -0
  15. data/lib/devise_saml_authenticatable/saml_response.rb +16 -0
  16. data/lib/devise_saml_authenticatable/strategy.rb +10 -2
  17. data/lib/devise_saml_authenticatable/version.rb +1 -1
  18. data/spec/controllers/devise/saml_sessions_controller_spec.rb +118 -11
  19. data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
  20. data/spec/devise_saml_authenticatable/default_idp_entity_id_reader_spec.rb +34 -4
  21. data/spec/devise_saml_authenticatable/model_spec.rb +199 -5
  22. data/spec/devise_saml_authenticatable/saml_mapped_attributes_spec.rb +50 -0
  23. data/spec/devise_saml_authenticatable/strategy_spec.rb +18 -0
  24. data/spec/features/saml_authentication_spec.rb +45 -21
  25. data/spec/rails_helper.rb +6 -2
  26. data/spec/routes/routes_spec.rb +102 -0
  27. data/spec/spec_helper.rb +7 -0
  28. data/spec/support/Gemfile.rails4 +24 -6
  29. data/spec/support/Gemfile.rails5 +25 -0
  30. data/spec/support/Gemfile.rails5.1 +25 -0
  31. data/spec/support/Gemfile.rails5.2 +25 -0
  32. data/spec/support/attribute-map.yml +12 -0
  33. data/spec/support/attribute_map_resolver.rb.erb +14 -0
  34. data/spec/support/idp_settings_adapter.rb.erb +5 -5
  35. data/spec/support/idp_template.rb +8 -1
  36. data/spec/support/rails_app.rb +110 -16
  37. data/spec/support/saml_idp_controller.rb.erb +22 -10
  38. data/spec/support/sp_template.rb +52 -21
  39. metadata +26 -10
  40. data/spec/support/Gemfile.ruby-saml-1.3 +0 -23
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+ require "devise_saml_authenticatable/default_attribute_map_resolver"
3
+
4
+ describe DeviseSamlAuthenticatable::DefaultAttributeMapResolver do
5
+ let!(:rails) { class_double("Rails", env: "test", logger: logger, root: rails_root).as_stubbed_const }
6
+ let(:logger) { instance_double("Logger", info: nil) }
7
+ let(:rails_root) { Pathname.new("tmp") }
8
+
9
+ let(:saml_response) { instance_double("OneLogin::RubySaml::Response") }
10
+ let(:file_contents) {
11
+ <<YAML
12
+ ---
13
+ firstname: first_name
14
+ lastname: last_name
15
+ YAML
16
+ }
17
+ before do
18
+ allow(File).to receive(:exist?).and_return(true)
19
+ allow(File).to receive(:read).and_return(file_contents)
20
+ end
21
+
22
+ describe "#attribute_map" do
23
+ it "reads the attribute map from the config file" do
24
+ expect(described_class.new(saml_response).attribute_map).to eq(
25
+ "firstname" => "first_name",
26
+ "lastname" => "last_name",
27
+ )
28
+ expect(File).to have_received(:read).with(Pathname.new("tmp").join("config", "attribute-map.yml"))
29
+ end
30
+
31
+ context "when the attribute map is broken down by environment" do
32
+ let(:file_contents) {
33
+ <<YAML
34
+ ---
35
+ test:
36
+ first: first_name
37
+ last: last_name
38
+ YAML
39
+ }
40
+ it "reads the attribute map from the environment key" do
41
+ expect(described_class.new(saml_response).attribute_map).to eq(
42
+ "first" => "first_name",
43
+ "last" => "last_name",
44
+ )
45
+ end
46
+ end
47
+
48
+ context "when the config file does not exist" do
49
+ before do
50
+ allow(File).to receive(:exist?).and_return(false)
51
+ end
52
+
53
+ it "is an empty hash" do
54
+ expect(described_class.new(saml_response).attribute_map).to eq({})
55
+ end
56
+ end
57
+ end
58
+ end
@@ -5,18 +5,48 @@ describe DeviseSamlAuthenticatable::DefaultIdpEntityIdReader do
5
5
  context "when there is a SAMLRequest in the params" do
6
6
  let(:params) { {SAMLRequest: "logout request"} }
7
7
  let(:slo_logout_request) { double('slo_logout_request', issuer: 'meow')}
8
+ before do
9
+ allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(slo_logout_request)
10
+ end
11
+
8
12
  it "uses an OneLogin::RubySaml::SloLogoutrequest to get the idp_entity_id" do
9
- expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(slo_logout_request)
10
- described_class.entity_id(params)
13
+ expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).with("logout request", hash_including)
14
+ expect(described_class.entity_id(params)).to eq("meow")
15
+ end
16
+
17
+ context "and allowed_clock_drift is configured" do
18
+ before do
19
+ allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
20
+ end
21
+
22
+ it "allows the configured clock drift" do
23
+ expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).with("logout request", hash_including(allowed_clock_drift: 30))
24
+ expect(described_class.entity_id(params)).to eq("meow")
25
+ end
11
26
  end
12
27
  end
13
28
 
14
29
  context "when there is a SAMLResponse in the params" do
15
30
  let(:params) { {SAMLResponse: "auth response"} }
16
31
  let(:response) { double('response', issuers: ['meow'] )}
32
+ before do
33
+ allow(OneLogin::RubySaml::Response).to receive(:new).and_return(response)
34
+ end
35
+
17
36
  it "uses an OneLogin::RubySaml::Response to get the idp_entity_id" do
18
- expect(OneLogin::RubySaml::Response).to receive(:new).and_return(response)
19
- described_class.entity_id(params)
37
+ expect(OneLogin::RubySaml::Response).to receive(:new).with("auth response", hash_including)
38
+ expect(described_class.entity_id(params)).to eq("meow")
39
+ end
40
+
41
+ context "and allowed_clock_drift is configured" do
42
+ before do
43
+ allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
44
+ end
45
+
46
+ it "allows the configured clock drift" do
47
+ expect(OneLogin::RubySaml::Response).to receive(:new).with("auth response", hash_including(allowed_clock_drift: 30))
48
+ expect(described_class.entity_id(params)).to eq("meow")
49
+ end
20
50
  end
21
51
  end
22
52
  end
@@ -32,6 +32,7 @@ describe Devise::Models::SamlAuthenticatable do
32
32
  end
33
33
 
34
34
  before do
35
+ allow(Devise).to receive(:saml_attribute_map_resolver).and_return(attribute_map_resolver)
35
36
  allow(Devise).to receive(:saml_default_user_key).and_return(:email)
36
37
  allow(Devise).to receive(:saml_create_user).and_return(false)
37
38
  allow(Devise).to receive(:saml_use_subject).and_return(false)
@@ -39,13 +40,19 @@ describe Devise::Models::SamlAuthenticatable do
39
40
 
40
41
  before do
41
42
  allow(Rails).to receive(:root).and_return("/railsroot")
42
- allow(File).to receive(:read).with("/railsroot/config/attribute-map.yml").and_return(<<-ATTRIBUTEMAP)
43
- ---
44
- "saml-email-format": email
45
- "saml-name-format": name
46
- ATTRIBUTEMAP
47
43
  end
48
44
 
45
+ let(:attribute_map_resolver) {
46
+ Class.new(::DeviseSamlAuthenticatable::DefaultAttributeMapResolver) do
47
+ def attribute_map
48
+ {
49
+ "saml-email-format" => "email",
50
+ "saml-name-format" => "name",
51
+ }
52
+ end
53
+ end
54
+ }
55
+ let(:attributemap) { attribute_map_resolver.new(nil).attribute_map }
49
56
  let(:response) { double(:response, attributes: attributes, name_id: name_id) }
50
57
  let(:attributes) {
51
58
  OneLogin::RubySaml::Attributes.new(
@@ -178,4 +185,191 @@ describe Devise::Models::SamlAuthenticatable do
178
185
  include_examples "correct downcasing"
179
186
  end
180
187
  end
188
+
189
+ context "when configured with a resource validator class" do
190
+ let(:validator_class) { double("validator") }
191
+ let(:validator) { double("validator") }
192
+ let(:user) { Model.new(new_record: false) }
193
+
194
+ before do
195
+ allow(Devise).to receive(:saml_resource_validator).and_return(validator_class)
196
+ allow(validator_class).to receive(:new).and_return(validator)
197
+ end
198
+
199
+ context "and sent a valid value" do
200
+ before do
201
+ allow(validator).to receive(:validate).with(user, response).and_return(true)
202
+ end
203
+
204
+ it "returns the user" do
205
+ expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
206
+ expect(Model.authenticate_with_saml(response, nil)).to eq(user)
207
+ end
208
+ end
209
+
210
+ context "and sent an invalid value" do
211
+ before do
212
+ allow(validator).to receive(:validate).with(user, response).and_return(false)
213
+ end
214
+
215
+ it "returns nil" do
216
+ expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
217
+ expect(Model.authenticate_with_saml(response, nil)).to be_nil
218
+ end
219
+ end
220
+ end
221
+
222
+
223
+ context "when configured with a resource validator hook" do
224
+ let(:validator_hook) { double("validator_hook") }
225
+ let(:decorated_response) { ::SamlAuthenticatable::SamlResponse.new(response, attributemap) }
226
+ let(:user) { Model.new(new_record: false) }
227
+
228
+ before do
229
+ allow(Devise).to receive(:saml_resource_validator_hook).and_return(validator_hook)
230
+ allow(::SamlAuthenticatable::SamlResponse).to receive(:new).with(response, attributemap).and_return(decorated_response)
231
+ end
232
+
233
+ context "and sent a valid value" do
234
+ before do
235
+ expect(validator_hook).to receive(:call).with(user, decorated_response, 'user@example.com').and_return(true)
236
+ end
237
+
238
+ it "returns the user" do
239
+ expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
240
+ expect(Model.authenticate_with_saml(response, nil)).to eq(user)
241
+ end
242
+ end
243
+
244
+ context "and sent an invalid value" do
245
+ before do
246
+ expect(validator_hook).to receive(:call).with(user, decorated_response, 'user@example.com').and_return(false)
247
+ end
248
+
249
+ it "returns nil" do
250
+ expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
251
+ expect(Model.authenticate_with_saml(response, nil)).to be_nil
252
+ end
253
+ end
254
+ end
255
+
256
+
257
+ context "when configured to use a custom update hook" do
258
+ it "can replicate the default behaviour in a custom hook" do
259
+ configure_hook do |user, saml_response|
260
+ Devise.saml_default_update_resource_hook.call(user, saml_response)
261
+ end
262
+
263
+ new_user = Model.authenticate_with_saml(response, nil)
264
+
265
+ expect(new_user.name).to eq(attributes['saml-name-format'])
266
+ expect(new_user.email).to eq(attributes['saml-email-format'])
267
+ end
268
+
269
+ it "can extend the default behaviour with custom transformations" do
270
+ configure_hook do |user, saml_response|
271
+ Devise.saml_default_update_resource_hook.call(user, saml_response)
272
+
273
+ user.email = "ext+#{user.email}"
274
+ end
275
+
276
+ new_user = Model.authenticate_with_saml(response, nil)
277
+
278
+ expect(new_user.name).to eq(attributes['saml-name-format'])
279
+ expect(new_user.email).to eq("ext+#{attributes['saml-email-format']}")
280
+ end
281
+
282
+ it "can extend the default behaviour using information from the saml response" do
283
+ configure_hook do |user, saml_response|
284
+ Devise.saml_default_update_resource_hook.call(user, saml_response)
285
+
286
+ name_id = saml_response.raw_response.name_id
287
+ user.name += "@#{name_id}"
288
+ end
289
+
290
+ new_user = Model.authenticate_with_saml(response, nil)
291
+
292
+ expect(new_user.name).to eq("#{attributes['saml-name-format']}@#{response.name_id}")
293
+ expect(new_user.email).to eq(attributes['saml-email-format'])
294
+ end
295
+
296
+ def configure_hook(&block)
297
+ allow(Model).to receive(:where).with(email: 'user@example.com').and_return([])
298
+ allow(Devise).to receive(:saml_default_user_key).and_return(:email)
299
+ allow(Devise).to receive(:saml_create_user).and_return(true)
300
+ allow(Devise).to receive(:saml_update_resource_hook).and_return(block)
301
+ end
302
+ end
303
+
304
+ context "when configured to use a custom user locator" do
305
+ let(:name_id) { 'SomeUsername' }
306
+
307
+ it "can replicate the default behaviour for a new user in a custom locator" do
308
+ allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([])
309
+
310
+ configure_hook do |model, saml_response, auth_value|
311
+ Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
312
+ end
313
+
314
+ new_user = Model.authenticate_with_saml(response, nil)
315
+
316
+ expect(new_user.name).to eq(attributes['saml-name-format'])
317
+ expect(new_user.email).to eq(attributes['saml-email-format'])
318
+ end
319
+
320
+ it "can replicate the default behaviour for an existing user in a custom locator" do
321
+ user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
322
+ user.save!
323
+
324
+ allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([user])
325
+
326
+ configure_hook do |model, saml_response, auth_value|
327
+ Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
328
+ end
329
+
330
+ new_user = Model.authenticate_with_saml(response, nil)
331
+
332
+ expect(new_user).to eq(user)
333
+ expect(new_user.name).to eq(attributes['saml-name-format'])
334
+ expect(new_user.email).to eq(attributes['saml-email-format'])
335
+ end
336
+
337
+ it "can change the default behaviour for a new user from the saml response" do
338
+ allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([])
339
+
340
+ configure_hook do |model, saml_response, auth_value|
341
+ name_id = saml_response.raw_response.name_id
342
+ model.where(foo: auth_value, bar: name_id).first
343
+ end
344
+
345
+ new_user = Model.authenticate_with_saml(response, nil)
346
+
347
+ expect(new_user.name).to eq(attributes['saml-name-format'])
348
+ expect(new_user.email).to eq(attributes['saml-email-format'])
349
+ end
350
+
351
+ it "can change the default behaviour for an existing user from the saml response" do
352
+ user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
353
+ user.save!
354
+
355
+ allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([user])
356
+
357
+ configure_hook do |model, saml_response, auth_value|
358
+ name_id = saml_response.raw_response.name_id
359
+ model.where(foo: auth_value, bar: name_id).first
360
+ end
361
+
362
+ new_user = Model.authenticate_with_saml(response, nil)
363
+
364
+ expect(new_user).to eq(user)
365
+ expect(new_user.name).to eq(attributes['saml-name-format'])
366
+ expect(new_user.email).to eq(attributes['saml-email-format'])
367
+ end
368
+
369
+ def configure_hook(&block)
370
+ allow(Devise).to receive(:saml_default_user_key).and_return(:email)
371
+ allow(Devise).to receive(:saml_create_user).and_return(true)
372
+ allow(Devise).to receive(:saml_resource_locator).and_return(block)
373
+ end
374
+ end
181
375
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'devise_saml_authenticatable/saml_mapped_attributes'
3
+
4
+ describe SamlAuthenticatable::SamlMappedAttributes do
5
+ let(:instance) { described_class.new(saml_attributes, attribute_map) }
6
+ let(:attribute_map_file) { File.join(File.dirname(__FILE__), '../support/attribute-map.yml') }
7
+ let(:attribute_map) { YAML.load(File.read(attribute_map_file)) }
8
+ let(:saml_attributes) do
9
+ {
10
+ "first_name" => ["John"],
11
+ "last_name"=>["Smith"],
12
+ "email"=>["john.smith@example.com"]
13
+ }
14
+ end
15
+
16
+ describe "#value_by_resource_key" do
17
+ RSpec.shared_examples "correctly maps the value of the resource key" do |saml_key, resource_key, expected_value|
18
+ subject(:perform) { instance.value_by_resource_key(resource_key) }
19
+
20
+ it "correctly maps the resource key, #{resource_key}, to the value of the '#{saml_key}' SAML key" do
21
+ saml_attributes[saml_key] = saml_attributes.delete(resource_key)
22
+ expect(perform).to eq(expected_value)
23
+ end
24
+ end
25
+
26
+ context "first_name" do
27
+ saml_keys = ['urn:mace:dir:attribute-def:first_name', 'first_name', 'firstName', 'firstname']
28
+
29
+ saml_keys.each do |saml_key|
30
+ include_examples 'correctly maps the value of the resource key', saml_key, 'first_name', ['John']
31
+ end
32
+ end
33
+
34
+ context 'last_name' do
35
+ saml_keys = ['urn:mace:dir:attribute-def:last_name', 'last_name', 'lastName', 'lastname']
36
+
37
+ saml_keys.each do |saml_key|
38
+ include_examples 'correctly maps the value of the resource key', saml_key, 'last_name', ['Smith']
39
+ end
40
+ end
41
+
42
+ context 'email' do
43
+ saml_keys = ['urn:mace:dir:attribute-def:email', 'email_address', 'emailAddress', 'email']
44
+
45
+ saml_keys.each do |saml_key|
46
+ include_examples 'correctly maps the value of the resource key', saml_key, 'email', ['john.smith@example.com']
47
+ end
48
+ end
49
+ end
50
+ end
@@ -134,6 +134,24 @@ describe Devise::Strategies::SamlAuthenticatable do
134
134
  strategy.authenticate!
135
135
  end
136
136
  end
137
+
138
+ context "when allowed_clock_drift is configured" do
139
+ before do
140
+ allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
141
+ end
142
+
143
+ it "is valid with the configured clock drift" do
144
+ expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(allowed_clock_drift: 30))
145
+ expect(strategy).to be_valid
146
+ end
147
+
148
+ it "authenticates with the configured clock drift" do
149
+ expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(allowed_clock_drift: 30))
150
+
151
+ expect(strategy).to receive(:success!).with(user)
152
+ strategy.authenticate!
153
+ end
154
+ end
137
155
  end
138
156
 
139
157
  it "is not valid without a SAMLResponse parameter" do
@@ -5,6 +5,7 @@ require 'uri'
5
5
  require 'capybara/rspec'
6
6
  require 'capybara/poltergeist'
7
7
  Capybara.default_driver = :poltergeist
8
+ Capybara.server = :webrick
8
9
 
9
10
  describe "SAML Authentication", type: :feature do
10
11
  let(:idp_port) { 8009 }
@@ -56,7 +57,7 @@ describe "SAML Authentication", type: :feature do
56
57
  expect(current_url).to eq("http://localhost:8020/")
57
58
 
58
59
  click_on "Log out"
59
- #confirm the logout response redirected to the SP which in turn attempted to sign th e
60
+ # confirm the logout response redirected to the SP which in turn attempted to sign the user back in
60
61
  expect(current_url).to match(%r(\Ahttp://localhost:8009/saml/auth\?SAMLRequest=))
61
62
 
62
63
  # prove user is now signed out
@@ -84,8 +85,8 @@ describe "SAML Authentication", type: :feature do
84
85
  @sp_pid = start_app('sp', sp_port)
85
86
  end
86
87
  after(:each) do
87
- stop_app(@idp_pid)
88
- stop_app(@sp_pid)
88
+ stop_app("idp", @idp_pid)
89
+ stop_app("sp", @sp_pid)
89
90
  end
90
91
 
91
92
  it_behaves_like "it authenticates and creates users"
@@ -99,8 +100,8 @@ describe "SAML Authentication", type: :feature do
99
100
  @sp_pid = start_app('sp', sp_port)
100
101
  end
101
102
  after(:each) do
102
- stop_app(@idp_pid)
103
- stop_app(@sp_pid)
103
+ stop_app("idp", @idp_pid)
104
+ stop_app("sp", @sp_pid)
104
105
  end
105
106
 
106
107
  it_behaves_like "it authenticates and creates users"
@@ -114,8 +115,8 @@ describe "SAML Authentication", type: :feature do
114
115
  @sp_pid = start_app('sp', sp_port)
115
116
  end
116
117
  after(:each) do
117
- stop_app(@idp_pid)
118
- stop_app(@sp_pid)
118
+ stop_app("idp", @idp_pid)
119
+ stop_app("sp", @sp_pid)
119
120
  end
120
121
 
121
122
  it_behaves_like "it authenticates and creates users"
@@ -130,33 +131,33 @@ describe "SAML Authentication", type: :feature do
130
131
  @sp_pid = start_app('sp', sp_port)
131
132
  end
132
133
  after(:each) do
133
- stop_app(@idp_pid)
134
- stop_app(@sp_pid)
134
+ stop_app("idp", @idp_pid)
135
+ stop_app("sp", @sp_pid)
135
136
  end
136
137
 
137
138
  it_behaves_like "it authenticates and creates users"
138
139
  end
139
140
 
140
141
  context "when the idp_settings_adapter key is set" do
141
-
142
142
  before(:each) do
143
143
  create_app('idp', 'INCLUDE_SUBJECT_IN_ATTRIBUTES' => "false")
144
144
  create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "true", 'IDP_SETTINGS_ADAPTER' => "IdpSettingsAdapter", 'IDP_ENTITY_ID_READER' => "OurEntityIdReader")
145
145
 
146
- @idp_pid = start_app('idp', idp_port)
146
+ # use a different port for this entity ID; configured in spec/support/idp_settings_adapter.rb.erb
147
+ @idp_pid = start_app('idp', 8010)
147
148
  @sp_pid = start_app('sp', sp_port)
148
149
  end
149
150
 
150
151
  after(:each) do
151
- stop_app(@idp_pid)
152
- stop_app(@sp_pid)
152
+ stop_app("idp", @idp_pid)
153
+ stop_app("sp", @sp_pid)
153
154
  end
154
155
 
155
156
  it "authenticates an existing user on a SP via an IdP" do
156
157
  create_user("you@example.com")
157
158
 
158
159
  visit 'http://localhost:8020/users/saml/sign_in/?entity_id=http%3A%2F%2Flocalhost%3A8020%2Fsaml%2Fmetadata'
159
- expect(current_url).to match(%r(\Ahttp://www.example.com/\?SAMLRequest=))
160
+ expect(current_url).to match(%r(\Ahttp://localhost:8010/saml/auth\?SAMLRequest=))
160
161
  end
161
162
  end
162
163
 
@@ -171,8 +172,8 @@ describe "SAML Authentication", type: :feature do
171
172
  end
172
173
 
173
174
  after(:each) do
174
- stop_app(@idp_pid)
175
- stop_app(@sp_pid)
175
+ stop_app("idp", @idp_pid)
176
+ stop_app("sp", @sp_pid)
176
177
  end
177
178
 
178
179
  it_behaves_like "it authenticates and creates users"
@@ -187,24 +188,47 @@ describe "SAML Authentication", type: :feature do
187
188
  fill_in "Email", with: "you@example.com"
188
189
  fill_in "Password", with: "asdf"
189
190
  click_on "Sign in"
190
- expect(page).to have_content("Example Domain This domain is established to be used for illustrative examples in documents. You may use this domain in examples without prior coordination or asking for permission.")
191
+ expect(page).to have_content(:all, "Example Domain This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.")
191
192
  expect(current_url).to eq("http://www.example.com/")
192
193
  end
193
194
  end
194
195
  end
195
196
 
197
+ context "when the saml_attribute_map is set" do
198
+ before(:each) do
199
+ create_app(
200
+ "idp",
201
+ "EMAIL_ADDRESS_ATTRIBUTE_KEY" => "myemailaddress",
202
+ "NAME_ATTRIBUTE_KEY" => "myname",
203
+ "INCLUDE_SUBJECT_IN_ATTRIBUTES" => "false",
204
+ )
205
+ create_app(
206
+ "sp",
207
+ "ATTRIBUTE_MAP_RESOLVER" => "AttributeMapResolver",
208
+ "USE_SUBJECT_TO_AUTHENTICATE" => "true",
209
+ )
210
+ @idp_pid = start_app("idp", idp_port)
211
+ @sp_pid = start_app("sp", sp_port)
212
+ end
213
+ after(:each) do
214
+ stop_app("idp", @idp_pid)
215
+ stop_app("sp", @sp_pid)
216
+ end
217
+
218
+ it_behaves_like "it authenticates and creates users"
219
+ end
220
+
196
221
  def create_user(email)
197
222
  response = Net::HTTP.post_form(URI('http://localhost:8020/users'), email: email)
198
223
  expect(response.code).to eq('201')
199
224
  end
200
225
 
201
- def sign_in
202
- visit 'http://localhost:8020/'
203
- expect(current_url).to match(%r(\Ahttp://localhost:8009/saml/auth\?SAMLRequest=))
226
+ def sign_in(entity_id: "")
227
+ visit "http://localhost:8020/users/saml/sign_in/?entity_id=#{URI.escape(entity_id)}"
204
228
  fill_in "Email", with: "you@example.com"
205
229
  fill_in "Password", with: "asdf"
206
230
  click_on "Sign in"
207
- Timeout.timeout(Capybara.default_wait_time) do
231
+ Timeout.timeout(Capybara.default_max_wait_time) do
208
232
  loop do
209
233
  sleep 0.1
210
234
  break if current_url == "http://localhost:8020/"