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.
- checksums.yaml +5 -5
- data/.gitignore +0 -2
- data/.travis.yml +20 -21
- data/Gemfile +2 -2
- data/README.md +62 -18
- data/app/controllers/devise/saml_sessions_controller.rb +34 -7
- data/lib/devise_saml_authenticatable.rb +14 -1
- data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
- data/lib/devise_saml_authenticatable/exception.rb +1 -1
- data/lib/devise_saml_authenticatable/model.rb +8 -11
- data/lib/devise_saml_authenticatable/saml_config.rb +18 -2
- data/lib/devise_saml_authenticatable/strategy.rb +1 -1
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +69 -11
- data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
- data/spec/devise_saml_authenticatable/model_spec.rb +19 -8
- data/spec/features/saml_authentication_spec.rb +44 -37
- data/spec/rails_helper.rb +2 -2
- data/spec/spec_helper.rb +7 -0
- data/spec/support/Gemfile.rails4 +20 -10
- data/spec/support/Gemfile.rails5 +13 -2
- data/spec/support/Gemfile.rails5.1 +13 -2
- data/spec/support/Gemfile.rails5.2 +25 -0
- data/spec/support/attribute_map_resolver.rb.erb +14 -0
- data/spec/support/idp_settings_adapter.rb.erb +5 -5
- data/spec/support/idp_template.rb +3 -1
- data/spec/support/rails_app.rb +75 -17
- data/spec/support/saml_idp_controller.rb.erb +13 -6
- data/spec/support/sp_template.rb +43 -21
- metadata +13 -8
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
|
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(
|
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'
|
data/spec/support/Gemfile.rails4
CHANGED
@@ -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.
|
16
|
-
gem '
|
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
|
data/spec/support/Gemfile.rails5
CHANGED
@@ -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: "
|
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:
|
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://
|
12
|
-
idp_sso_target_url: "http://
|
13
|
-
|
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',
|
12
|
+
gem 'ruby-saml-idp', '~> 0.3.3'
|
11
13
|
gem 'thin'
|
12
14
|
|
13
15
|
insert_into_file('Gemfile', after: /\z/) {
|
data/spec/support/rails_app.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
-
|
21
|
-
rails_new_options
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
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
|
-
|
50
|
-
|
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
|
-
|
20
|
+
name_attribute_key => "A User",
|
21
21
|
}
|
22
22
|
if include_subject_in_attributes
|
23
|
-
attributes[
|
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:
|
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[
|
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:
|
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:
|
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
|
|
data/spec/support/sp_template.rb
CHANGED
@@ -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
|
-
|
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 :
|
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', ''
|