decidim-msad 0.22.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.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +476 -0
- data/Rakefile +17 -0
- data/app/controllers/decidim/msad/omniauth_callbacks_controller.rb +178 -0
- data/app/controllers/decidim/msad/sessions_controller.rb +58 -0
- data/app/controllers/decidim/msad/verification/authorizations_controller.rb +19 -0
- data/config/locales/en.yml +27 -0
- data/config/locales/fi.yml +26 -0
- data/config/locales/sv.yml +26 -0
- data/lib/decidim/msad.rb +198 -0
- data/lib/decidim/msad/authentication.rb +4 -0
- data/lib/decidim/msad/authentication/authenticator.rb +229 -0
- data/lib/decidim/msad/authentication/errors.rb +21 -0
- data/lib/decidim/msad/engine.rb +112 -0
- data/lib/decidim/msad/mail_interceptors.rb +9 -0
- data/lib/decidim/msad/mail_interceptors/generated_recipients_interceptor.rb +25 -0
- data/lib/decidim/msad/test/runtime.rb +38 -0
- data/lib/decidim/msad/verification.rb +5 -0
- data/lib/decidim/msad/verification/engine.rb +43 -0
- data/lib/decidim/msad/verification/manager.rb +17 -0
- data/lib/decidim/msad/verification/metadata_collector.rb +39 -0
- data/lib/decidim/msad/version.rb +8 -0
- data/lib/generators/decidim/msad/install_generator.rb +126 -0
- data/lib/generators/templates/msad_initializer.rb +74 -0
- data/lib/generators/templates/msad_initializer_test.rb +5 -0
- data/lib/omniauth/msad/metadata.rb +46 -0
- data/lib/omniauth/msad/settings.rb +9 -0
- data/lib/omniauth/msad/test.rb +10 -0
- data/lib/omniauth/msad/test/certificate_generator.rb +51 -0
- data/lib/omniauth/strategies/msad.rb +187 -0
- metadata +116 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Msad
|
5
|
+
module Verification
|
6
|
+
# This is an engine that performs user authorization.
|
7
|
+
class Engine < ::Rails::Engine
|
8
|
+
isolate_namespace Decidim::Msad::Verification
|
9
|
+
|
10
|
+
paths["db/migrate"] = nil
|
11
|
+
paths["lib/tasks"] = nil
|
12
|
+
|
13
|
+
routes do
|
14
|
+
resource :authorizations, only: [:new], as: :authorization
|
15
|
+
|
16
|
+
root to: "authorizations#new"
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer "decidim_msad.verification_workflow", after: :load_config_initializers do
|
20
|
+
next unless Decidim::Msad.configured?
|
21
|
+
|
22
|
+
# We cannot use the name `:msad` for the verification workflow
|
23
|
+
# because otherwise the route namespace (decidim_msad) would
|
24
|
+
# conflict with the main engine controlling the authentication flows.
|
25
|
+
# The main problem that this would bring is that the root path for
|
26
|
+
# this engine would not be found.
|
27
|
+
Decidim::Verifications.register_workflow(:msad_identity) do |workflow|
|
28
|
+
workflow.engine = Decidim::Msad::Verification::Engine
|
29
|
+
|
30
|
+
Decidim::Msad::Verification::Manager.configure_workflow(workflow)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_seed
|
35
|
+
# Enable the `:msad_identity` authorization
|
36
|
+
org = Decidim::Organization.first
|
37
|
+
org.available_authorizations << :msad_identity
|
38
|
+
org.save!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Msad
|
5
|
+
module Verification
|
6
|
+
class Manager
|
7
|
+
def self.configure_workflow(workflow)
|
8
|
+
Decidim::Msad.workflow_configurator.call(workflow)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.metadata_collector_for(saml_attributes)
|
12
|
+
Decidim::Msad.metadata_collector_class.new(saml_attributes)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Msad
|
5
|
+
module Verification
|
6
|
+
class MetadataCollector
|
7
|
+
def initialize(saml_attributes)
|
8
|
+
@saml_attributes = saml_attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
def metadata
|
12
|
+
return nil unless Decidim::Msad.metadata_attributes.is_a?(Hash)
|
13
|
+
return nil if Decidim::Msad.metadata_attributes.blank?
|
14
|
+
|
15
|
+
collect.delete_if { |_k, v| v.nil? }
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
attr_reader :saml_attributes
|
21
|
+
|
22
|
+
def collect
|
23
|
+
Decidim::Msad.metadata_attributes.map do |key, defs|
|
24
|
+
value = begin
|
25
|
+
case defs
|
26
|
+
when Hash
|
27
|
+
saml_attributes.public_send(defs[:type], defs[:name])
|
28
|
+
when String
|
29
|
+
saml_attributes.single(defs)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
[key, value]
|
34
|
+
end.to_h
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/base"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Msad
|
7
|
+
module Generators
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path("../../templates", __dir__)
|
10
|
+
|
11
|
+
desc "Creates a Devise initializer and copy locale files to your application."
|
12
|
+
|
13
|
+
class_option(
|
14
|
+
:dummy_cert,
|
15
|
+
desc: "Defines whether to create a dummy certificate for localhost.",
|
16
|
+
type: :boolean,
|
17
|
+
default: false
|
18
|
+
)
|
19
|
+
|
20
|
+
class_option(
|
21
|
+
:test_initializer,
|
22
|
+
desc: "Copies the test initializer instead of the actual one (for test dummy app).",
|
23
|
+
type: :boolean,
|
24
|
+
default: false,
|
25
|
+
hide: true
|
26
|
+
)
|
27
|
+
|
28
|
+
def copy_initializer
|
29
|
+
if options[:test_initializer]
|
30
|
+
copy_file "msad_initializer_test.rb", "config/initializers/msad.rb"
|
31
|
+
else
|
32
|
+
copy_file "msad_initializer.rb", "config/initializers/msad.rb"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def enable_authentication
|
37
|
+
secrets_path = Rails.application.root.join("config", "secrets.yml")
|
38
|
+
secrets = YAML.safe_load(File.read(secrets_path), [], [], true)
|
39
|
+
|
40
|
+
if secrets["default"]["omniauth"]["msad"]
|
41
|
+
say_status :identical, "config/secrets.yml", :blue
|
42
|
+
else
|
43
|
+
mod = SecretsModifier.new(secrets_path)
|
44
|
+
final = mod.modify
|
45
|
+
|
46
|
+
target_path = Rails.application.root.join("config", "secrets.yml")
|
47
|
+
File.open(target_path, "w") { |f| f.puts final }
|
48
|
+
|
49
|
+
say_status :insert, "config/secrets.yml", :green
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class SecretsModifier
|
54
|
+
def initialize(filepath)
|
55
|
+
@filepath = filepath
|
56
|
+
end
|
57
|
+
|
58
|
+
def modify
|
59
|
+
self.inside_config = false
|
60
|
+
self.inside_omniauth = false
|
61
|
+
self.config_branch = nil
|
62
|
+
@final = ""
|
63
|
+
|
64
|
+
@empty_line_count = 0
|
65
|
+
File.readlines(filepath).each do |line|
|
66
|
+
if line =~ /^$/
|
67
|
+
@empty_line_count += 1
|
68
|
+
next
|
69
|
+
else
|
70
|
+
handle_line line
|
71
|
+
insert_empty_lines
|
72
|
+
end
|
73
|
+
|
74
|
+
@final += line
|
75
|
+
end
|
76
|
+
insert_empty_lines
|
77
|
+
|
78
|
+
@final
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_accessor :filepath, :empty_line_count, :inside_config, :inside_omniauth, :config_branch
|
84
|
+
|
85
|
+
def handle_line(line)
|
86
|
+
if inside_config && line =~ /^ omniauth:/
|
87
|
+
self.inside_omniauth = true
|
88
|
+
elsif inside_omniauth && (line =~ /^( )?[a-z]+/ || line =~ /^#.*/)
|
89
|
+
inject_msad_config
|
90
|
+
self.inside_omniauth = false
|
91
|
+
end
|
92
|
+
|
93
|
+
return unless line =~ /^[a-z]+/
|
94
|
+
|
95
|
+
# A new root configuration block starts
|
96
|
+
self.inside_config = false
|
97
|
+
self.inside_omniauth = false
|
98
|
+
|
99
|
+
branch = line[/^(default|development|test):/, 1]
|
100
|
+
if branch
|
101
|
+
self.inside_config = true
|
102
|
+
self.config_branch = branch.to_sym
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def insert_empty_lines
|
107
|
+
@final += "\n" * empty_line_count
|
108
|
+
@empty_line_count = 0
|
109
|
+
end
|
110
|
+
|
111
|
+
def inject_msad_config
|
112
|
+
@final += " msad:\n"
|
113
|
+
case config_branch
|
114
|
+
when :development, :test
|
115
|
+
@final += " enabled: true\n"
|
116
|
+
else
|
117
|
+
@final += " enabled: false\n"
|
118
|
+
end
|
119
|
+
@final += " metadata_url:\n"
|
120
|
+
@final += " icon: account-login\n"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Decidim::Msad.configure do |config|
|
4
|
+
# Define the IdP metadata URL through the secrets
|
5
|
+
config.idp_metadata_url = Rails.application.secrets.omniauth[:msad][:metadata_url]
|
6
|
+
|
7
|
+
# Define the service provider entity ID:
|
8
|
+
# config.sp_entity_id = "https://www.example.org/users/auth/msad/metadata"
|
9
|
+
# Or define it in your application configuration and apply it here:
|
10
|
+
# config.sp_entity_id = Rails.application.config.msad_entity_id
|
11
|
+
# Enable automatically assigned emails
|
12
|
+
config.auto_email_domain = "example.org"
|
13
|
+
|
14
|
+
# Subscribe new users automatically to newsletters (default false).
|
15
|
+
#
|
16
|
+
# IMPORANT NOTE:
|
17
|
+
# Legally it should be always a user's own decision if the want to subscribe
|
18
|
+
# to any newsletters or not. Before enabling this, make sure you have your
|
19
|
+
# legal basis covered for enabling it. E.g. for internal instances within
|
20
|
+
# organizations, it should be generally acceptable but please confirm that
|
21
|
+
# from the legal department first!
|
22
|
+
# config.registration_newsletter_subscriptions = true
|
23
|
+
|
24
|
+
# Configure the SAML attributes that will be stored in the user's
|
25
|
+
# authorization metadata.
|
26
|
+
# config.metadata_attributes = {
|
27
|
+
# display_name: "http://schemas.microsoft.com/identity/claims/displayname",
|
28
|
+
# given_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
|
29
|
+
# surname: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
|
30
|
+
# birthday: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth",
|
31
|
+
# postal_code: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode",
|
32
|
+
# mobile_phone: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone",
|
33
|
+
# groups: { name: "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups", type: :multi }
|
34
|
+
# }
|
35
|
+
|
36
|
+
# You can define extra service provider metadata that will show up in the
|
37
|
+
# metadata URL.
|
38
|
+
# config.sp_metadata = [
|
39
|
+
# {
|
40
|
+
# name: "Organization",
|
41
|
+
# children: [
|
42
|
+
# {
|
43
|
+
# name: "OrganizationName",
|
44
|
+
# attributes: { "xml:lang" => "en-US" },
|
45
|
+
# content: "Acme"
|
46
|
+
# },
|
47
|
+
# {
|
48
|
+
# name: "OrganizationDisplayName",
|
49
|
+
# attributes: { "xml:lang" => "en-US" },
|
50
|
+
# content: "Acme Corporation"
|
51
|
+
# },
|
52
|
+
# {
|
53
|
+
# name: "OrganizationURL",
|
54
|
+
# attributes: { "xml:lang" => "en-US" },
|
55
|
+
# content: "https://en.wikipedia.org/wiki/Acme_Corporation"
|
56
|
+
# }
|
57
|
+
# ]
|
58
|
+
# },
|
59
|
+
# {
|
60
|
+
# name: "ContactPerson",
|
61
|
+
# attributes: { "contactType" => "technical" },
|
62
|
+
# children: [
|
63
|
+
# {
|
64
|
+
# name: "GivenName",
|
65
|
+
# content: "John Doe"
|
66
|
+
# },
|
67
|
+
# {
|
68
|
+
# name: "EmailAddress",
|
69
|
+
# content: "jdoe@acme.org"
|
70
|
+
# }
|
71
|
+
# ]
|
72
|
+
# }
|
73
|
+
# ]
|
74
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module MSAD
|
5
|
+
class Metadata < OneLogin::RubySaml::Metadata
|
6
|
+
def generate(settings, pretty_print = false)
|
7
|
+
metadata_signed = settings.security.delete(:metadata_signed)
|
8
|
+
settings.security[:metadata_signed] = false
|
9
|
+
|
10
|
+
meta_xml = super(settings)
|
11
|
+
meta_doc = XMLSecurity::Document.new(meta_xml)
|
12
|
+
add_tags_to(meta_doc.root, settings.sp_metadata) if settings.sp_metadata
|
13
|
+
|
14
|
+
sign_document!(meta_doc, settings) if metadata_signed
|
15
|
+
return meta_doc.write("", 1) if pretty_print
|
16
|
+
|
17
|
+
meta_doc.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def add_tags_to(parent, tags)
|
23
|
+
tags.each do |tag|
|
24
|
+
element = parent.add_element "md:#{tag[:name]}", tag[:attributes]
|
25
|
+
element.text = tag[:content] if tag[:content]
|
26
|
+
add_tags_to(element, tag[:children]) if tag[:children].is_a?(Array)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign_document!(document, settings)
|
31
|
+
return unless settings.security[:metadata_signed]
|
32
|
+
return unless settings.private_key
|
33
|
+
return unless settings.certificate
|
34
|
+
|
35
|
+
# embed signature
|
36
|
+
private_key = settings.get_sp_key
|
37
|
+
document.sign_document(
|
38
|
+
private_key,
|
39
|
+
cert,
|
40
|
+
settings.security[:signature_method],
|
41
|
+
settings.security[:digest_method]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Msad
|
5
|
+
module Test
|
6
|
+
class CertificateGenerator
|
7
|
+
def private_key
|
8
|
+
@private_key ||= OpenSSL::PKey::RSA.new(2048)
|
9
|
+
end
|
10
|
+
|
11
|
+
def certificate
|
12
|
+
@certificate ||= begin
|
13
|
+
public_key = private_key.public_key
|
14
|
+
|
15
|
+
subject = "/C=FI/O=Test/OU=Test/CN=Test"
|
16
|
+
|
17
|
+
cert = OpenSSL::X509::Certificate.new
|
18
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
|
19
|
+
cert.not_before = Time.now
|
20
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
21
|
+
cert.public_key = public_key
|
22
|
+
cert.serial = 0x0
|
23
|
+
cert.version = 2
|
24
|
+
|
25
|
+
inject_certificate_extensions(cert)
|
26
|
+
|
27
|
+
cert.sign(private_key, OpenSSL::Digest::SHA1.new)
|
28
|
+
|
29
|
+
cert
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def inject_certificate_extensions(cert)
|
36
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
37
|
+
ef.subject_certificate = cert
|
38
|
+
ef.issuer_certificate = cert
|
39
|
+
cert.extensions = [
|
40
|
+
ef.create_extension("basicConstraints", "CA:TRUE", true),
|
41
|
+
ef.create_extension("subjectKeyIdentifier", "hash")
|
42
|
+
]
|
43
|
+
cert.add_extension ef.create_extension(
|
44
|
+
"authorityKeyIdentifier",
|
45
|
+
"keyid:always,issuer:always"
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "omniauth-saml"
|
4
|
+
require "omniauth/msad/metadata"
|
5
|
+
require "omniauth/msad/settings"
|
6
|
+
|
7
|
+
module OmniAuth
|
8
|
+
module Strategies
|
9
|
+
class MSAD < SAML
|
10
|
+
# The IdP metadata URL.
|
11
|
+
option :idp_metadata_url, nil
|
12
|
+
|
13
|
+
# These are the requested attributes that could be defined for the
|
14
|
+
# metadata. However, these can be already defined at the federation side,
|
15
|
+
# so they are not generally needed with AD based federations.
|
16
|
+
option :request_attributes, []
|
17
|
+
|
18
|
+
# Maps the SAML attributes to OmniAuth info schema:
|
19
|
+
# https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later
|
20
|
+
option(
|
21
|
+
:attribute_statements,
|
22
|
+
name: %w(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name),
|
23
|
+
email: %w(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress),
|
24
|
+
first_name: %w(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname),
|
25
|
+
last_name: %w(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname),
|
26
|
+
nickname: %w(http://schemas.microsoft.com/identity/claims/displayname)
|
27
|
+
)
|
28
|
+
|
29
|
+
option(:sp_metadata, [])
|
30
|
+
|
31
|
+
info do
|
32
|
+
found_attributes = options.attribute_statements.map do |key, values|
|
33
|
+
attribute = find_attribute_by(values)
|
34
|
+
[key, attribute]
|
35
|
+
end
|
36
|
+
info_hash = Hash[found_attributes]
|
37
|
+
|
38
|
+
# The name attribute is overridden if the first name and last name are
|
39
|
+
# defined because otherwise it could be the principal name which is not
|
40
|
+
# a user readable name as expected by OmniAuth.
|
41
|
+
name = "#{info_hash["first_name"]} #{info_hash["last_name"]}".strip
|
42
|
+
info_hash["name"] = name unless name.blank?
|
43
|
+
|
44
|
+
info_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(app, *args, &block)
|
48
|
+
super
|
49
|
+
|
50
|
+
# Add the request attributes to the options.
|
51
|
+
options[:sp_name_qualifier] = options[:sp_entity_id] if options[:sp_name_qualifier].nil?
|
52
|
+
|
53
|
+
# Remove the nil options from the origianl options array that will be
|
54
|
+
# defined by the MSAD options
|
55
|
+
[
|
56
|
+
:idp_name_qualifier,
|
57
|
+
:name_identifier_format,
|
58
|
+
:security
|
59
|
+
].each do |key|
|
60
|
+
options.delete(key) if options[key].nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add the MSAD options to the local options, most of which are fetched
|
64
|
+
# from the metadata. The options array is the one that gets priority in
|
65
|
+
# case it overrides some of the metadata or locally defined option
|
66
|
+
# values.
|
67
|
+
@options = OmniAuth::Strategy::Options.new(
|
68
|
+
msad_options.merge(options)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method can be used externally to fetch information about the
|
73
|
+
# response, e.g. in case of failures.
|
74
|
+
def response_object
|
75
|
+
return nil unless request.params["SAMLResponse"]
|
76
|
+
|
77
|
+
with_settings do |settings|
|
78
|
+
response = OneLogin::RubySaml::Response.new(
|
79
|
+
request.params["SAMLResponse"],
|
80
|
+
options_for_response_object.merge(settings: settings)
|
81
|
+
)
|
82
|
+
response.attributes["fingerprint"] = settings.idp_cert_fingerprint
|
83
|
+
response
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def msad_options
|
90
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
91
|
+
|
92
|
+
# Returns OneLogin::RubySaml::Settings prepopulated with idp metadata
|
93
|
+
settings = begin
|
94
|
+
begin
|
95
|
+
idp_metadata_parser.parse_remote_to_hash(
|
96
|
+
options.idp_metadata_url,
|
97
|
+
true
|
98
|
+
)
|
99
|
+
rescue ::URI::InvalidURIError
|
100
|
+
# Allow the OmniAuth strategy to be configured with empty settings
|
101
|
+
# in order to provide the metadata URL even when the authentication
|
102
|
+
# endpoint is not configured.
|
103
|
+
{}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Define the security settings as there are some defaults that need to be
|
108
|
+
# modified
|
109
|
+
security_defaults = OneLogin::RubySaml::Settings::DEFAULTS[:security]
|
110
|
+
settings[:security] = security_defaults.merge(
|
111
|
+
authn_requests_signed: options.certificate.present?,
|
112
|
+
want_assertions_signed: true,
|
113
|
+
digest_method: XMLSecurity::Document::SHA256,
|
114
|
+
signature_method: XMLSecurity::Document::RSA_SHA256
|
115
|
+
)
|
116
|
+
|
117
|
+
# Add some extra information that is necessary for correctly formatted
|
118
|
+
# logout requests.
|
119
|
+
settings[:idp_name_qualifier] = settings[:idp_entity_id]
|
120
|
+
|
121
|
+
if !options.name_identifier_format.blank?
|
122
|
+
# If the name identifier format has been configured, use that instead
|
123
|
+
# of the IdP metadata value. Otherwise the first format available in
|
124
|
+
# the IdP metadata would be used.
|
125
|
+
settings[:name_identifier_format] = options.name_identifier_format
|
126
|
+
elsif settings[:name_identifier_format].blank?
|
127
|
+
# If the name identifier format is not defined in the IdP metadata,
|
128
|
+
# add the persistent format to the SP metadata.
|
129
|
+
settings[:name_identifier_format] = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
130
|
+
end
|
131
|
+
|
132
|
+
settings
|
133
|
+
end
|
134
|
+
|
135
|
+
def with_settings
|
136
|
+
options[:assertion_consumer_service_url] ||= callback_url
|
137
|
+
yield OmniAuth::MSAD::Settings.new(options)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Customize the metadata class in order to add custom nodes to the
|
141
|
+
# metadata.
|
142
|
+
def other_phase_for_metadata
|
143
|
+
with_settings do |settings|
|
144
|
+
response = OmniAuth::MSAD::Metadata.new
|
145
|
+
|
146
|
+
add_request_attributes_to(settings) if options.request_attributes.length.positive?
|
147
|
+
|
148
|
+
Rack::Response.new(
|
149
|
+
response.generate(settings),
|
150
|
+
200,
|
151
|
+
"Content-Type" => "application/xml"
|
152
|
+
).finish
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# End the local user session BEFORE sending the logout request to the
|
157
|
+
# identity provider.
|
158
|
+
def other_phase_for_spslo
|
159
|
+
return super unless options.idp_slo_target_url
|
160
|
+
|
161
|
+
with_settings do |settings|
|
162
|
+
# Some session variables are needed when generating the logout request
|
163
|
+
request = generate_logout_request(settings)
|
164
|
+
# Destroy the local user session
|
165
|
+
options[:idp_slo_session_destroy].call @env, session
|
166
|
+
# Send the logout request to the identity provider
|
167
|
+
redirect(request)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Overridden to disable passing the relay state with a request parameter
|
172
|
+
# which is possible in the default implementation.
|
173
|
+
def slo_relay_state
|
174
|
+
state = super
|
175
|
+
|
176
|
+
# Ensure that we are only using the relay states to redirect the user
|
177
|
+
# within the current website. This forces the relay state to always
|
178
|
+
# start with a single forward slash character (/).
|
179
|
+
return "/" unless state =~ %r{^/[^/].*}
|
180
|
+
|
181
|
+
state
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
OmniAuth.config.add_camelization "msad", "MSAD"
|