devise_saml_authenticatable 1.5.0 → 1.6.3

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.
data/spec/rails_helper.rb CHANGED
@@ -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|
data/spec/spec_helper.rb CHANGED
@@ -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
  {}
@@ -1,5 +1,7 @@
1
1
  # Set up a SAML IdP
2
2
 
3
+ @email_address_attribute_key = ENV.fetch("EMAIL_ADDRESS_ATTRIBUTE_KEY", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
4
+ @name_attribute_key = ENV.fetch("NAME_ATTRIBUTE_KEY", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
3
5
  @include_subject_in_attributes = ENV.fetch('INCLUDE_SUBJECT_IN_ATTRIBUTES')
4
6
  @valid_destination = ENV.fetch('VALID_DESTINATION', "true")
5
7
 
@@ -7,7 +9,7 @@ if Rails::VERSION::MAJOR < 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::M
7
9
  gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "34814fd41f91c493b89aa01ac73c44d241a31245b5bc5542fa4b7317525e1dcfa60ba947b3d085e4e229456fdee0d8af6aac6a63cf750d807ea6fe5d853dff4a"'
8
10
  end
9
11
 
10
- gem 'ruby-saml-idp', git: "https://github.com/lawrencepit/ruby-saml-idp.git", ref: "ec715b252e849105c7a96df27b731c6e7f725a51"
12
+ gem 'ruby-saml-idp', '~> 0.3.3'
11
13
  gem 'thin'
12
14
 
13
15
  insert_into_file('Gemfile', after: /\z/) {
@@ -1,6 +1,7 @@
1
- require 'open3'
2
- require 'socket'
3
- require 'timeout'
1
+ require "open3"
2
+ require "socket"
3
+ require "tempfile"
4
+ require "timeout"
4
5
 
5
6
  APP_READY_TIMEOUT ||= 30
6
7
 
@@ -17,25 +18,32 @@ rescue Errno::ESRCH
17
18
  end
18
19
 
19
20
  def create_app(name, env = {})
20
- rails_new_options = %w(-T -J -S --skip-spring --skip-listen --skip-bootsnap)
21
- rails_new_options << "-O" if name == 'idp'
22
- Dir.chdir(File.expand_path('../../support', __FILE__)) do
23
- FileUtils.rm_rf(name)
24
- system(env, "rails", "new", name, *rails_new_options, "-m", "#{name}_template.rb")
21
+ puts "[#{name}] Creating Rails app"
22
+ rails_new_options = %w[-T -J -S --skip-spring --skip-listen --skip-bootsnap]
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
25
30
  end
26
31
  end
27
32
 
28
33
  def start_app(name, port, options = {})
34
+ puts "[#{name}] Starting Rails app"
29
35
  pid = nil
30
- Bundler.with_clean_env do
31
- Dir.chdir(File.expand_path("../../support/#{name}", __FILE__)) do
32
- pid = Process.spawn({"RAILS_ENV" => "production"}, "bundle exec rails server -p #{port} -e production", out: "log/#{name}.log", err: "log/#{name}.err.log")
36
+ app_bundle_install(name)
37
+
38
+ with_clean_env do
39
+ Dir.chdir(app_dir(name)) do
40
+ pid = Process.spawn(app_env(name), "bundle exec rails server -p #{port} -e production", chdir: app_dir(name), out: "log/#{name}.log", err: "log/#{name}.err.log")
33
41
  begin
34
- Timeout::timeout(APP_READY_TIMEOUT) do
42
+ Timeout.timeout(APP_READY_TIMEOUT) do
35
43
  sleep 1 until app_ready?(pid, port)
36
44
  end
37
45
  if app_ready?(pid, port)
38
- puts "Launched #{name} on port #{port} (pid #{pid})..."
46
+ puts "[#{name}] Launched #{name} on port #{port} (pid #{pid})..."
39
47
  else
40
48
  raise "#{name} failed after starting"
41
49
  end
@@ -46,16 +54,33 @@ def start_app(name, port, options = {})
46
54
  end
47
55
  pid
48
56
  rescue RuntimeError => e
49
- $stdout.puts "#{File.read(File.expand_path("../../support/#{name}/log/#{name}.log", __FILE__))}"
50
- $stderr.puts "#{File.read(File.expand_path("../../support/#{name}/log/#{name}.err.log", __FILE__))}"
57
+ warn "=== #{name}"
58
+ Dir.chdir(app_dir(name)) do
59
+ warn File.read("log/#{name}.log") if File.exist?("log/#{name}.log")
60
+ warn File.read("log/#{name}.err.log") if File.exist?("log/#{name}.err.log")
61
+ end
51
62
  raise e
52
63
  end
53
64
 
54
- def stop_app(pid)
65
+ def stop_app(name, pid)
55
66
  if pid
56
67
  Process.kill(:INT, pid)
57
68
  Process.wait(pid)
58
69
  end
70
+ Dir.chdir(app_dir(name)) do
71
+ if File.exist?("log/#{name}.log")
72
+ puts "=== [#{name}] stdout"
73
+ puts File.read("log/#{name}.log")
74
+ end
75
+ if File.exist?("log/#{name}.err.log")
76
+ warn "=== [#{name}] stderr"
77
+ warn File.read("log/#{name}.err.log")
78
+ end
79
+ if File.exist?("log/production.log")
80
+ puts "=== [#{name}] Rails logs"
81
+ puts File.read("log/production.log")
82
+ end
83
+ end
59
84
  end
60
85
 
61
86
  def port_open?(port)
@@ -64,7 +89,7 @@ def port_open?(port)
64
89
  s = TCPSocket.new('localhost', port)
65
90
  s.close
66
91
  return true
67
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
92
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL
68
93
  # try 127.0.0.1
69
94
  end
70
95
  begin
@@ -78,3 +103,36 @@ def port_open?(port)
78
103
  rescue Timeout::Error
79
104
  false
80
105
  end
106
+
107
+ def app_bundle_install(name)
108
+ with_clean_env do
109
+ Open3.popen3(app_env(name), "bundle install", chdir: app_dir(name)) do |stdin, stdout, stderr, thread|
110
+ stdin.close
111
+ exit_status = thread.value
112
+
113
+ puts stdout.read
114
+ warn stderr.read
115
+ raise "bundle install failed" unless exit_status.success?
116
+ end
117
+ end
118
+ end
119
+
120
+ def app_dir(name)
121
+ File.join(working_directory, name)
122
+ end
123
+
124
+ def app_env(name)
125
+ {"BUNDLE_GEMFILE" => File.join(app_dir(name), "Gemfile"), "RAILS_ENV" => "production"}
126
+ end
127
+
128
+ def working_directory
129
+ $working_directory ||= Dir.mktmpdir("dsa_test")
130
+ end
131
+
132
+ def with_clean_env(&blk)
133
+ if Bundler.respond_to?(:with_original_env)
134
+ Bundler.with_original_env(&blk)
135
+ else
136
+ Bundler.with_clean_env(&blk)
137
+ end
138
+ end
@@ -17,10 +17,10 @@ class SamlIdpController < SamlIdp::IdpController
17
17
 
18
18
  def idp_make_saml_response(_)
19
19
  attributes = {
20
- "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" => "A User",
20
+ name_attribute_key => "A User",
21
21
  }
22
22
  if include_subject_in_attributes
23
- attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] = "you@example.com"
23
+ attributes[email_address_attribute_key] = "you@example.com"
24
24
  end
25
25
  encode_SAMLResponse("you@example.com", attributes: attributes)
26
26
  end
@@ -33,6 +33,13 @@ class SamlIdpController < SamlIdp::IdpController
33
33
  }
34
34
  end
35
35
 
36
+ def email_address_attribute_key
37
+ "<%= @email_address_attribute_key %>"
38
+ end
39
+
40
+ def name_attribute_key
41
+ "<%= @name_attribute_key %>"
42
+ end
36
43
 
37
44
  def encode_SAMLResponse(nameID, opts = {})
38
45
  now = Time.now.utc
@@ -50,7 +57,7 @@ class SamlIdpController < SamlIdp::IdpController
50
57
  attribute_statement = ""
51
58
  end
52
59
 
53
- assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{session_index}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer>#{issuer_uri}</Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions>#{attribute_statement}<AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{session_index}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]
60
+ assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{session_index}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer>#{issuer_uri}</Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions>#{attribute_statement}<AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{session_index}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]
54
61
 
55
62
  digest_value = Base64.encode64(algorithm.digest(assertion)).gsub(/\n/, '')
56
63
 
@@ -115,7 +122,7 @@ class SamlIdpController < SamlIdp::IdpController
115
122
  def idp_make_saml_slo_response(person)
116
123
  attributes = {}
117
124
  if include_subject_in_attributes
118
- attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] = "you@example.com"
125
+ attributes[email_address_attribute_key] = "you@example.com"
119
126
  end
120
127
  encode_SAML_SLO_Response("you@example.com", attributes: attributes)
121
128
  end
@@ -148,7 +155,7 @@ class SamlIdpController < SamlIdp::IdpController
148
155
  audience_uri = opts[:audience_uri] || (@saml_slo_acs_url && @saml_slo_acs_url[/^(.*?\/\/.*?\/)/, 1])
149
156
  issuer_uri = opts[:issuer_uri] || (defined?(request) && request.url.split("?")[0]) || "http://example.com"
150
157
 
151
- assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{session_index}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer2>#{issuer_uri}</Issuer2><Subject><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_slo_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_slo_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"><AttributeValue>#{nameID}</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{session_index}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]
158
+ assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{session_index}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer2>#{issuer_uri}</Issuer2><Subject><NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_slo_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_slo_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name="#{email_address_attribute_key}"><AttributeValue>#{nameID}</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{session_index}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]
152
159
 
153
160
  digest_value = Base64.encode64(algorithm.digest(assertion)).gsub(/\n/, '')
154
161
 
@@ -189,7 +196,7 @@ class SamlIdpController < SamlIdp::IdpController
189
196
  Destination="#{destination(@saml_slo_acs_url)}"
190
197
  IssueInstant="#{now.iso8601}">
191
198
  <saml:Issuer >#{issuer_uri}</saml:Issuer>
192
- <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{nameID}</saml:NameID>
199
+ <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">#{nameID}</saml:NameID>
193
200
  <samlp:SessionIndex>_#{session_index}</samlp:SessionIndex>
194
201
  </samlp:LogoutRequest>]
195
202
 
@@ -2,17 +2,18 @@
2
2
 
3
3
  require "onelogin/ruby-saml/version"
4
4
 
5
+ attribute_map_resolver = ENV.fetch("ATTRIBUTE_MAP_RESOLVER", "nil")
5
6
  saml_session_index_key = ENV.fetch('SAML_SESSION_INDEX_KEY', ":session_index")
6
7
  use_subject_to_authenticate = ENV.fetch('USE_SUBJECT_TO_AUTHENTICATE')
7
8
  idp_settings_adapter = ENV.fetch('IDP_SETTINGS_ADAPTER', "nil")
8
- idp_entity_id_reader = ENV.fetch('IDP_ENTITY_ID_READER', "DeviseSamlAuthenticatable::DefaultIdpEntityIdReader")
9
+ idp_entity_id_reader = ENV.fetch('IDP_ENTITY_ID_READER', '"DeviseSamlAuthenticatable::DefaultIdpEntityIdReader"')
9
10
  saml_failed_callback = ENV.fetch('SAML_FAILED_CALLBACK', "nil")
10
11
 
11
12
  if Rails::VERSION::MAJOR < 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR < 2)
12
13
  gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "8b5889df1fcf03f76c7d66da02d8776bcc85b06bed7d9c592f076d9c8a5455ee6d4beae45986c3c030b40208db5e612f2a6ef8283036a352e3fae83c5eda36be"'
13
14
  end
14
15
 
15
- gem 'devise_saml_authenticatable', path: '../../..'
16
+ gem 'devise_saml_authenticatable', path: File.expand_path("../../..", __FILE__)
16
17
  gem 'ruby-saml', OneLogin::RubySaml::VERSION
17
18
  gem 'thin'
18
19
 
@@ -22,17 +23,26 @@ insert_into_file('Gemfile', after: /\z/) {
22
23
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
23
24
  gem 'devise', '~> 3.5'
24
25
  gem 'nokogiri', '~> 1.6.8'
26
+ elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
27
+ gem 'responders', '~> 2.4'
25
28
  end
26
29
  GEMFILE
27
30
  }
31
+ if Rails::VERSION::MAJOR < 6
32
+ # sqlite3 is hard-coded in Rails < 6 to v1.3.x
33
+ gsub_file 'Gemfile', /^gem 'sqlite3'.*$/, "gem 'sqlite3', '~> 1.3.6'"
34
+ end
28
35
 
36
+ template File.expand_path('../attribute_map_resolver.rb.erb', __FILE__), 'app/lib/attribute_map_resolver.rb'
29
37
  template File.expand_path('../idp_settings_adapter.rb.erb', __FILE__), 'app/lib/idp_settings_adapter.rb'
30
38
 
31
- create_file 'config/attribute-map.yml', <<-ATTRIBUTES
39
+ if attribute_map_resolver == "nil"
40
+ create_file 'config/attribute-map.yml', <<-ATTRIBUTES
32
41
  ---
33
42
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": email
34
43
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": name
35
- ATTRIBUTES
44
+ ATTRIBUTES
45
+ end
36
46
 
37
47
  create_file('app/lib/our_saml_failed_callback_handler.rb', <<-CALLBACKHANDLER)
38
48
 
@@ -61,22 +71,6 @@ end
61
71
  READER
62
72
 
63
73
  after_bundle do
64
- generate :controller, 'home', 'index'
65
- insert_into_file('app/controllers/home_controller.rb', after: "class HomeController < ApplicationController\n") {
66
- <<-AUTHENTICATE
67
- before_action :authenticate_user!
68
- AUTHENTICATE
69
- }
70
- insert_into_file('app/views/home/index.html.erb', after: /\z/) {
71
- <<-HOME
72
- <%= current_user.email %> <%= current_user.name %>
73
- <%= form_tag destroy_user_session_path(entity_id: "http://localhost:8020/saml/metadata"), method: :delete do %>
74
- <%= submit_tag "Log out" %>
75
- <% end %>
76
- HOME
77
- }
78
- route "root to: 'home#index'"
79
-
80
74
  # Configure for our SAML IdP
81
75
  generate 'devise:install'
82
76
  gsub_file 'config/initializers/devise.rb', /^end$/, <<-CONFIG
@@ -85,6 +79,9 @@ after_bundle do
85
79
  config.saml_default_user_key = :email
86
80
  config.saml_session_index_key = #{saml_session_index_key}
87
81
 
82
+ if #{attribute_map_resolver}
83
+ config.saml_attribute_map_resolver = #{attribute_map_resolver}
84
+ end
88
85
  config.saml_use_subject = #{use_subject_to_authenticate}
89
86
  config.saml_create_user = true
90
87
  config.saml_update_user = true
@@ -98,11 +95,33 @@ after_bundle do
98
95
  settings.idp_slo_target_url = "http://localhost:8009/saml/logout"
99
96
  settings.idp_sso_target_url = "http://localhost:8009/saml/auth"
100
97
  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
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
101
99
  end
102
100
  end
103
101
  CONFIG
104
102
 
105
- generate :devise, "user", "email:string", "name:string", "session_index:string"
103
+ generate :controller, 'home', 'index'
104
+ insert_into_file('app/controllers/home_controller.rb', after: "class HomeController < ApplicationController\n") {
105
+ <<-AUTHENTICATE
106
+ before_action :authenticate_user!
107
+ AUTHENTICATE
108
+ }
109
+ insert_into_file('app/views/home/index.html.erb', after: /\z/) {
110
+ <<-HOME
111
+ <%= current_user.email %> <%= current_user.name %>
112
+ <%= form_tag destroy_user_session_path(entity_id: "http://localhost:8020/saml/metadata"), method: :delete do %>
113
+ <%= submit_tag "Log out" %>
114
+ <% end %>
115
+ HOME
116
+ }
117
+ route "root to: 'home#index'"
118
+
119
+ if Rails::VERSION::MAJOR < 6
120
+ generate :devise, "user", "email:string", "name:string", "session_index:string"
121
+ else
122
+ # devise seems to add `email` by default in Rails 6
123
+ generate :devise, "user", "name:string", "session_index:string"
124
+ end
106
125
  gsub_file 'app/models/user.rb', /database_authenticatable.*\n.*/, 'saml_authenticatable'
107
126
  route "resources :users, only: [:create]"
108
127
  create_file('app/controllers/users_controller.rb', <<-USERS)
@@ -119,6 +138,9 @@ end
119
138
  rake "db:migrate"
120
139
  rake "db:create", env: "production"
121
140
  rake "db:migrate", env: "production"
141
+
142
+ # Remove any specs so that future RSpec runs don't try to also run these
143
+ run 'rm -rf spec'
122
144
  end
123
145
 
124
146
  create_file 'public/stylesheets/application.css', ''