devise_saml_authenticatable 1.5.0 → 1.6.0

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