devise_saml_authenticatable 1.5.0 → 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.
@@ -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
@@ -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,15 +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
43
  end
44
44
 
45
- let(:attributemap) {<<-ATTRIBUTEMAP
46
- ---
47
- "saml-email-format": email
48
- "saml-name-format": name
49
- ATTRIBUTEMAP
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
50
54
  }
55
+ let(:attributemap) { attribute_map_resolver.new(nil).attribute_map }
51
56
  let(:response) { double(:response, attributes: attributes, name_id: name_id) }
52
57
  let(:attributes) {
53
58
  OneLogin::RubySaml::Attributes.new(
@@ -217,12 +222,12 @@ describe Devise::Models::SamlAuthenticatable do
217
222
 
218
223
  context "when configured with a resource validator hook" do
219
224
  let(:validator_hook) { double("validator_hook") }
220
- let(:decorated_response) { ::SamlAuthenticatable::SamlResponse.new(response, YAML.load(attributemap)) }
225
+ let(:decorated_response) { ::SamlAuthenticatable::SamlResponse.new(response, attributemap) }
221
226
  let(:user) { Model.new(new_record: false) }
222
227
 
223
228
  before do
224
229
  allow(Devise).to receive(:saml_resource_validator_hook).and_return(validator_hook)
225
- allow(::SamlAuthenticatable::SamlResponse).to receive(:new).with(response, YAML.load(attributemap)).and_return(decorated_response)
230
+ allow(::SamlAuthenticatable::SamlResponse).to receive(:new).with(response, attributemap).and_return(decorated_response)
226
231
  end
227
232
 
228
233
  context "and sent a valid value" do
@@ -57,7 +57,7 @@ describe "SAML Authentication", type: :feature do
57
57
  expect(current_url).to eq("http://localhost:8020/")
58
58
 
59
59
  click_on "Log out"
60
- #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
61
61
  expect(current_url).to match(%r(\Ahttp://localhost:8009/saml/auth\?SAMLRequest=))
62
62
 
63
63
  # prove user is now signed out
@@ -85,8 +85,8 @@ describe "SAML Authentication", type: :feature do
85
85
  @sp_pid = start_app('sp', sp_port)
86
86
  end
87
87
  after(:each) do
88
- stop_app(@idp_pid)
89
- stop_app(@sp_pid)
88
+ stop_app("idp", @idp_pid)
89
+ stop_app("sp", @sp_pid)
90
90
  end
91
91
 
92
92
  it_behaves_like "it authenticates and creates users"
@@ -100,8 +100,8 @@ describe "SAML Authentication", type: :feature do
100
100
  @sp_pid = start_app('sp', sp_port)
101
101
  end
102
102
  after(:each) do
103
- stop_app(@idp_pid)
104
- stop_app(@sp_pid)
103
+ stop_app("idp", @idp_pid)
104
+ stop_app("sp", @sp_pid)
105
105
  end
106
106
 
107
107
  it_behaves_like "it authenticates and creates users"
@@ -115,8 +115,8 @@ describe "SAML Authentication", type: :feature do
115
115
  @sp_pid = start_app('sp', sp_port)
116
116
  end
117
117
  after(:each) do
118
- stop_app(@idp_pid)
119
- stop_app(@sp_pid)
118
+ stop_app("idp", @idp_pid)
119
+ stop_app("sp", @sp_pid)
120
120
  end
121
121
 
122
122
  it_behaves_like "it authenticates and creates users"
@@ -131,8 +131,8 @@ describe "SAML Authentication", type: :feature do
131
131
  @sp_pid = start_app('sp', sp_port)
132
132
  end
133
133
  after(:each) do
134
- stop_app(@idp_pid)
135
- stop_app(@sp_pid)
134
+ stop_app("idp", @idp_pid)
135
+ stop_app("sp", @sp_pid)
136
136
  end
137
137
 
138
138
  it_behaves_like "it authenticates and creates users"
@@ -143,37 +143,21 @@ describe "SAML Authentication", type: :feature 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/sso\?SAMLRequest=))
160
- end
161
-
162
- it "logs a user out of the IdP via the SP" do
163
- sign_in
164
-
165
- # prove user is still signed in
166
- visit 'http://localhost:8020/'
167
- expect(page).to have_content("you@example.com")
168
- expect(current_url).to eq("http://localhost:8020/")
169
-
170
- click_on "Log out"
171
- #confirm the logout response redirected to the SP which in turn attempted to sign th e
172
- expect(current_url).to match(%r(\Ahttp://www.example.com/slo\?SAMLRequest=))
173
-
174
- # prove user is now signed out
175
- visit 'http://localhost:8020/users/saml/sign_in/?entity_id=http%3A%2F%2Flocalhost%3A8020%2Fsaml%2Fmetadata'
176
- expect(current_url).to match(%r(\Ahttp://www.example.com/sso\?SAMLRequest=))
160
+ expect(current_url).to match(%r(\Ahttp://localhost:8010/saml/auth\?SAMLRequest=))
177
161
  end
178
162
  end
179
163
 
@@ -188,8 +172,8 @@ describe "SAML Authentication", type: :feature do
188
172
  end
189
173
 
190
174
  after(:each) do
191
- stop_app(@idp_pid)
192
- stop_app(@sp_pid)
175
+ stop_app("idp", @idp_pid)
176
+ stop_app("sp", @sp_pid)
193
177
  end
194
178
 
195
179
  it_behaves_like "it authenticates and creates users"
@@ -204,20 +188,43 @@ describe "SAML Authentication", type: :feature do
204
188
  fill_in "Email", with: "you@example.com"
205
189
  fill_in "Password", with: "asdf"
206
190
  click_on "Sign in"
207
- expect(page).to have_content(:all, "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.")
208
192
  expect(current_url).to eq("http://www.example.com/")
209
193
  end
210
194
  end
211
195
  end
212
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
+
213
221
  def create_user(email)
214
222
  response = Net::HTTP.post_form(URI('http://localhost:8020/users'), email: email)
215
223
  expect(response.code).to eq('201')
216
224
  end
217
225
 
218
- def sign_in
219
- visit 'http://localhost:8020/'
220
- 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)}"
221
228
  fill_in "Email", with: "you@example.com"
222
229
  fill_in "Password", with: "asdf"
223
230
  click_on "Sign in"
@@ -3,7 +3,7 @@ ENV["RAILS_ENV"] ||= 'test'
3
3
  require 'spec_helper'
4
4
 
5
5
  create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "false")
6
- require 'support/sp/config/environment'
6
+ require "#{working_directory}/sp/config/environment"
7
7
  require 'rspec/rails'
8
8
 
9
9
  ActiveRecord::Migration.verbose = false
@@ -11,7 +11,7 @@ ActiveRecord::Base.logger = Logger.new(nil)
11
11
  if ActiveRecord::Base.connection.respond_to?(:migration_context)
12
12
  ActiveRecord::Base.connection.migration_context.migrate
13
13
  else
14
- ActiveRecord::Migrator.migrate(File.expand_path("../support/sp/db/migrate/", __FILE__))
14
+ ActiveRecord::Migrator.migrate("#{working_directory}/sp/db/migrate/")
15
15
  end
16
16
 
17
17
  RSpec.configure do |config|
@@ -1,3 +1,5 @@
1
+ require "fileutils"
2
+
1
3
  RSpec.configure do |config|
2
4
  config.run_all_when_everything_filtered = true
3
5
  config.filter_run :focus
@@ -28,8 +30,13 @@ RSpec.configure do |config|
28
30
  Devise.saml_session_index_key = @original_saml_session_index_key
29
31
  Devise.idp_settings_adapter = nil
30
32
  end
33
+
34
+ config.after :suite do
35
+ FileUtils.rm_rf($working_directory) if $working_directory
36
+ end
31
37
  end
32
38
 
33
39
  require 'support/rails_app'
34
40
 
41
+ require "action_controller" # https://github.com/heartcombo/responders/pull/95
35
42
  require 'devise_saml_authenticatable'
@@ -6,26 +6,36 @@ gemspec path: '../..'
6
6
  group :test do
7
7
  gem 'rspec', '~> 3.0'
8
8
  gem 'rails', '~> 4.0'
9
- gem 'rspec-rails'
10
- gem 'sqlite3'
9
+ gem 'rspec-rails', '~> 3.9'
10
+ gem 'sqlite3', '~> 1.3.6'
11
11
  gem 'capybara'
12
12
  gem 'poltergeist'
13
13
 
14
14
  # 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 'addressable', '~> 2.4.0'
17
- gem 'mime-types', '~> 2.99'
18
- gem 'public_suffix', '~> 1.4.6'
19
- gem 'rake', '~> 12.2.0'
20
- elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
21
- gem 'public_suffix', '~> 2.0.5'
22
- gem 'rake'
15
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
16
+ gem 'rake', '~> 12.2'
23
17
  else
24
18
  gem 'rake'
25
19
  end
26
20
 
27
21
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
28
22
  gem 'devise', '~> 3.5'
23
+ gem 'minitest', '~> 5.11.0'
29
24
  gem 'nokogiri', '~> 1.6.8'
25
+ gem 'public_suffix', '~> 2.0.5'
26
+ end
27
+
28
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
29
+ gem 'responders', '~> 1.0'
30
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
31
+ gem 'responders', '~> 2.0'
32
+ end
33
+
34
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.2")
35
+ gem 'byebug', '~> 9.0'
36
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
37
+ gem 'byebug', '~> 10.0'
38
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
39
+ gem 'byebug', '~> 11.0.0'
30
40
  end
31
41
  end
@@ -7,8 +7,19 @@ group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
9
  gem 'rails', '~> 5.0.0'
10
- gem 'rspec-rails'
11
- gem 'sqlite3'
10
+ gem 'rspec-rails', '~> 3.9'
11
+ gem 'sqlite3', '~> 1.3.6'
12
12
  gem 'capybara'
13
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.4")
17
+ gem 'responders', '~> 2.4'
18
+ end
19
+
20
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
21
+ gem 'byebug', '~> 10.0'
22
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
23
+ gem 'byebug', '~> 11.0.0'
24
+ end
14
25
  end
@@ -7,8 +7,19 @@ group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
9
  gem 'rails', '~> 5.1.0'
10
- gem 'rspec-rails'
11
- gem 'sqlite3'
10
+ gem 'rspec-rails', '~> 3.9'
11
+ gem 'sqlite3', '~> 1.3.6'
12
12
  gem 'capybara'
13
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.4")
17
+ gem 'responders', '~> 2.4'
18
+ end
19
+
20
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
21
+ gem 'byebug', '~> 10.0'
22
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
23
+ gem 'byebug', '~> 11.0.0'
24
+ end
14
25
  end
@@ -0,0 +1,25 @@
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.2'
10
+ gem 'rspec-rails', '~> 3.9'
11
+ gem 'sqlite3', '~> 1.3.6'
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.4")
17
+ gem 'responders', '~> 2.4'
18
+ end
19
+
20
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
21
+ gem 'byebug', '~> 10.0'
22
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
23
+ gem 'byebug', '~> 11.0.0'
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ class AttributeMapResolver < DeviseSamlAuthenticatable::DefaultAttributeMapResolver
2
+ def attribute_map
3
+ issuer = saml_response.issuers.first
4
+ Rails.logger.info("[#{self.class.name}] issuer=#{issuer.inspect}")
5
+ if issuer == "http://localhost:8009/saml/auth"
6
+ {
7
+ "myemailaddress" => "email",
8
+ "myname" => "name",
9
+ }
10
+ else
11
+ {}
12
+ end
13
+ end
14
+ end
@@ -2,15 +2,15 @@ class IdpSettingsAdapter
2
2
  def self.settings(idp_entity_id)
3
3
  if idp_entity_id == "http://localhost:8020/saml/metadata"
4
4
  {
5
- assertion_consumer_service_url: "acs_url",
5
+ assertion_consumer_service_url: "http://localhost:8020/users/saml/auth",
6
6
  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
7
- name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
7
+ name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
8
8
  issuer: "sp_issuer",
9
9
  idp_entity_id: "http://localhost:8020/saml/metadata",
10
10
  authn_context: "",
11
- idp_slo_target_url: "http://www.example.com/slo",
12
- idp_sso_target_url: "http://www.example.com/sso",
13
- idp_cert: "idp_cert"
11
+ idp_slo_target_url: "http://localhost:8010/saml/logout",
12
+ 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
14
  }
15
15
  else
16
16
  {}