icn_saml_idp 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +238 -0
- data/app/controllers/saml_idp/idp_controller.rb +53 -0
- data/app/views/saml_idp/idp/new.html.erb +22 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +14 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +172 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +48 -0
- data/lib/saml_idp/controller.rb +128 -0
- data/lib/saml_idp/default.rb +49 -0
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/engine.rb +5 -0
- data/lib/saml_idp/hashable.rb +26 -0
- data/lib/saml_idp/incoming_metadata.rb +144 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +34 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +160 -0
- data/lib/saml_idp/name_id_formatter.rb +68 -0
- data/lib/saml_idp/persisted_metadata.rb +10 -0
- data/lib/saml_idp/request.rb +180 -0
- data/lib/saml_idp/response_builder.rb +62 -0
- data/lib/saml_idp/saml_response.rb +79 -0
- data/lib/saml_idp/service_provider.rb +76 -0
- data/lib/saml_idp/signable.rb +131 -0
- data/lib/saml_idp/signature_builder.rb +42 -0
- data/lib/saml_idp/signed_info_builder.rb +88 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +181 -0
- data/saml_idp.gemspec +65 -0
- data/spec/acceptance/acceptance_helper.rb +9 -0
- data/spec/acceptance/idp_controller_spec.rb +16 -0
- data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
- data/spec/lib/saml_idp/assertion_builder_spec.rb +106 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +53 -0
- data/spec/lib/saml_idp/configurator_spec.rb +43 -0
- data/spec/lib/saml_idp/controller_spec.rb +94 -0
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +42 -0
- data/spec/lib/saml_idp/request_spec.rb +106 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +42 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +68 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +27 -0
- data/spec/lib/saml_idp/signable_spec.rb +77 -0
- data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
- data/spec/rails_app/.gitignore +15 -0
- data/spec/rails_app/README.rdoc +261 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +60 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +37 -0
- data/spec/rails_app/config/environments/production.rb +67 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +6 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/doc/README_FOR_APP +2 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/rails_app/log/.gitkeep +0 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/test/fixtures/.gitkeep +0 -0
- data/spec/rails_app/test/functional/.gitkeep +0 -0
- data/spec/rails_app/test/integration/.gitkeep +0 -0
- data/spec/rails_app/test/performance/browsing_test.rb +12 -0
- data/spec/rails_app/test/test_helper.rb +13 -0
- data/spec/rails_app/test/unit/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/certificates/certificate1 +12 -0
- data/spec/support/certificates/r1_certificate2_base64 +1 -0
- data/spec/support/responses/adfs_response_sha1.xml +46 -0
- data/spec/support/responses/adfs_response_sha256.xml +46 -0
- data/spec/support/responses/adfs_response_sha384.xml +46 -0
- data/spec/support/responses/adfs_response_sha512.xml +46 -0
- data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
- data/spec/support/responses/no_signature_ns.xml +48 -0
- data/spec/support/responses/open_saml_response.xml +56 -0
- data/spec/support/responses/r1_response6.xml.base64 +1 -0
- data/spec/support/responses/response1.xml.base64 +1 -0
- data/spec/support/responses/response2.xml.base64 +79 -0
- data/spec/support/responses/response3.xml.base64 +66 -0
- data/spec/support/responses/response4.xml.base64 +93 -0
- data/spec/support/responses/response5.xml.base64 +102 -0
- data/spec/support/responses/response_with_ampersands.xml +139 -0
- data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
- data/spec/support/responses/simple_saml_php.xml +71 -0
- data/spec/support/responses/starfield_response.xml.base64 +1 -0
- data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
- data/spec/support/saml_request_macros.rb +38 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +137 -0
- metadata +465 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'xmlenc'
|
2
|
+
module SamlIdp
|
3
|
+
class Encryptor
|
4
|
+
attr_accessor :encryption_key
|
5
|
+
attr_accessor :block_encryption
|
6
|
+
attr_accessor :key_transport
|
7
|
+
attr_accessor :cert
|
8
|
+
|
9
|
+
def initialize(opts)
|
10
|
+
self.block_encryption = opts[:block_encryption]
|
11
|
+
self.key_transport = opts[:key_transport]
|
12
|
+
self.cert = opts[:cert]
|
13
|
+
end
|
14
|
+
|
15
|
+
def encrypt(raw_xml)
|
16
|
+
encryption_template = Nokogiri::XML::Document.parse(build_encryption_template).root
|
17
|
+
encrypted_data = Xmlenc::EncryptedData.new(encryption_template)
|
18
|
+
@encryption_key = encrypted_data.encrypt(raw_xml)
|
19
|
+
encrypted_key_node = encrypted_data.node.at_xpath(
|
20
|
+
'//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey',
|
21
|
+
Xmlenc::NAMESPACES
|
22
|
+
)
|
23
|
+
encrypted_key = Xmlenc::EncryptedKey.new(encrypted_key_node)
|
24
|
+
encrypted_key.encrypt(openssl_cert.public_key, encryption_key)
|
25
|
+
xml = Builder::XmlMarkup.new
|
26
|
+
xml.EncryptedAssertion xmlns: Saml::XML::Namespaces::ASSERTION do |enc_assert|
|
27
|
+
enc_assert << encrypted_data.node.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def openssl_cert
|
32
|
+
if cert.is_a?(String)
|
33
|
+
@_openssl_cert ||= OpenSSL::X509::Certificate.new(Base64.decode64(cert))
|
34
|
+
else
|
35
|
+
@_openssl_cert ||= cert
|
36
|
+
end
|
37
|
+
end
|
38
|
+
private :openssl_cert
|
39
|
+
|
40
|
+
def block_encryption_ns
|
41
|
+
"http://www.w3.org/2001/04/xmlenc##{block_encryption}"
|
42
|
+
end
|
43
|
+
private :block_encryption_ns
|
44
|
+
|
45
|
+
def key_transport_ns
|
46
|
+
"http://www.w3.org/2001/04/xmlenc##{key_transport}"
|
47
|
+
end
|
48
|
+
private :key_transport_ns
|
49
|
+
|
50
|
+
def cipher_algorithm
|
51
|
+
Xmlenc::EncryptedData::ALGORITHMS[block_encryption_ns]
|
52
|
+
end
|
53
|
+
private :cipher_algorithm
|
54
|
+
|
55
|
+
def build_encryption_template
|
56
|
+
xml = Builder::XmlMarkup.new
|
57
|
+
xml.EncryptedData Id: 'ED', Type: 'http://www.w3.org/2001/04/xmlenc#Element',
|
58
|
+
xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_data|
|
59
|
+
enc_data.EncryptionMethod Algorithm: block_encryption_ns
|
60
|
+
enc_data.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info|
|
61
|
+
key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
|
62
|
+
enc_key.EncryptionMethod Algorithm: key_transport_ns
|
63
|
+
enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
|
64
|
+
key_info2.tag! 'ds:KeyName'
|
65
|
+
key_info2.tag! 'ds:X509Data' do |x509_data|
|
66
|
+
x509_data.tag! 'ds:X509Certificate' do |x509_cert|
|
67
|
+
x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
enc_key.CipherData do |cipher_data|
|
72
|
+
cipher_data.CipherValue
|
73
|
+
end
|
74
|
+
enc_key.ReferenceList do |ref_list|
|
75
|
+
ref_list.DataReference URI: 'ED'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
enc_data.CipherData do |cipher_data|
|
80
|
+
cipher_data.CipherValue
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private :build_encryption_template
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SamlIdp
|
2
|
+
module Hashable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def hashables
|
6
|
+
self.class.hashables
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_h
|
10
|
+
hashables.reduce({}) do |hash, key|
|
11
|
+
hash[key.to_sym] = send(key)
|
12
|
+
hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def hashables
|
18
|
+
@hashables ||= Set.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def hashable(method_name)
|
22
|
+
self.hashables << method_name.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'saml_idp/hashable'
|
3
|
+
module SamlIdp
|
4
|
+
class IncomingMetadata
|
5
|
+
include Hashable
|
6
|
+
attr_accessor :raw
|
7
|
+
|
8
|
+
delegate :xpath, to: :document
|
9
|
+
private :xpath
|
10
|
+
|
11
|
+
def initialize(raw = "")
|
12
|
+
self.raw = raw
|
13
|
+
end
|
14
|
+
|
15
|
+
def document
|
16
|
+
@document ||= Saml::XML::Document.parse raw
|
17
|
+
end
|
18
|
+
|
19
|
+
def sign_assertions
|
20
|
+
doc = xpath(
|
21
|
+
"//md:SPSSODescriptor",
|
22
|
+
ds: signature_namespace,
|
23
|
+
md: metadata_namespace
|
24
|
+
).first
|
25
|
+
doc ? !!doc["WantAssertionsSigned"] : false
|
26
|
+
end
|
27
|
+
hashable :sign_assertions
|
28
|
+
|
29
|
+
def display_name
|
30
|
+
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
|
31
|
+
end
|
32
|
+
hashable :display_name
|
33
|
+
|
34
|
+
def contact_person
|
35
|
+
{
|
36
|
+
given_name: given_name,
|
37
|
+
surname: surname,
|
38
|
+
company: company,
|
39
|
+
telephone_number: telephone_number,
|
40
|
+
email_address: email_address
|
41
|
+
}
|
42
|
+
end
|
43
|
+
hashable :contact_person
|
44
|
+
|
45
|
+
def signing_certificate
|
46
|
+
xpath(
|
47
|
+
"//md:SPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
48
|
+
ds: signature_namespace,
|
49
|
+
md: metadata_namespace
|
50
|
+
).first.try(:content).to_s
|
51
|
+
end
|
52
|
+
hashable :signing_certificate
|
53
|
+
|
54
|
+
def encryption_certificate
|
55
|
+
xpath(
|
56
|
+
"//md:SPSSODescriptor/md:KeyDescriptor[@use='encryption']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
57
|
+
ds: signature_namespace,
|
58
|
+
md: metadata_namespace
|
59
|
+
).first.try(:content).to_s
|
60
|
+
end
|
61
|
+
hashable :encryption_certificate
|
62
|
+
|
63
|
+
def single_logout_services
|
64
|
+
xpath(
|
65
|
+
"//md:SPSSODescriptor/md:SingleLogoutService",
|
66
|
+
md: metadata_namespace
|
67
|
+
).reduce({}) do |hash, el|
|
68
|
+
hash[el["Binding"].to_s.split(":").last] = el["Location"]
|
69
|
+
hash
|
70
|
+
end
|
71
|
+
end
|
72
|
+
hashable :single_logout_services
|
73
|
+
|
74
|
+
def name_id_formats
|
75
|
+
xpath(
|
76
|
+
"//md:SPSSODescriptor/md:NameIDFormat",
|
77
|
+
md: metadata_namespace
|
78
|
+
).reduce(Set.new) do |set, el|
|
79
|
+
props = el.content.to_s.match /urn:oasis:names:tc:SAML:(?<version>\S+):nameid-format:(?<name>\S+)/
|
80
|
+
set << props[:name].to_s.underscore if props[:name].present?
|
81
|
+
set
|
82
|
+
end
|
83
|
+
end
|
84
|
+
hashable :name_id_formats
|
85
|
+
|
86
|
+
def assertion_consumer_services
|
87
|
+
xpath(
|
88
|
+
"//md:SPSSODescriptor/md:AssertionConsumerService",
|
89
|
+
md: metadata_namespace
|
90
|
+
).sort_by { |el| el["index"].to_i }.reduce([]) do |array, el|
|
91
|
+
props = el["Binding"].to_s.match /urn:oasis:names:tc:SAML:(?<version>\S+):bindings:(?<name>\S+)/
|
92
|
+
array << { binding: props[:name], location: el["Location"], default: !!el["isDefault"] }
|
93
|
+
array
|
94
|
+
end
|
95
|
+
end
|
96
|
+
hashable :assertion_consumer_services
|
97
|
+
|
98
|
+
def given_name
|
99
|
+
contact_person_document.xpath("//md:GivenName", md: metadata_namespace).first.try(:content).to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def surname
|
103
|
+
contact_person_document.xpath("//md:SurName", md: metadata_namespace).first.try(:content).to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
def company
|
107
|
+
contact_person_document.xpath("//md:Company", md: metadata_namespace).first.try(:content).to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def telephone_number
|
111
|
+
contact_person_document.xpath("//md:TelephoneNumber", md: metadata_namespace).first.try(:content).to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
def email_address
|
115
|
+
contact_person_document.xpath("//md:EmailAddress", md: metadata_namespace).first.try(:content).to_s.gsub("mailto:", "")
|
116
|
+
end
|
117
|
+
|
118
|
+
def role_descriptor_document
|
119
|
+
@role_descriptor ||= xpath("//md:RoleDescriptor", md: metadata_namespace).first
|
120
|
+
end
|
121
|
+
|
122
|
+
def service_provider_descriptor_document
|
123
|
+
@service_provider_descriptor ||= xpath("//md:SPSSODescriptor", md: metadata_namespace).first
|
124
|
+
end
|
125
|
+
|
126
|
+
def idp_descriptor_document
|
127
|
+
@idp_descriptor ||= xpath("//md:IDPSSODescriptor", md: metadata_namespace).first
|
128
|
+
end
|
129
|
+
|
130
|
+
def contact_person_document
|
131
|
+
@contact_person_document ||= xpath("//md:ContactPerson", md: metadata_namespace).first
|
132
|
+
end
|
133
|
+
|
134
|
+
def metadata_namespace
|
135
|
+
Saml::XML::Namespaces::METADATA
|
136
|
+
end
|
137
|
+
private :metadata_namespace
|
138
|
+
|
139
|
+
def signature_namespace
|
140
|
+
Saml::XML::Namespaces::SIGNATURE
|
141
|
+
end
|
142
|
+
private :signature_namespace
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutBuilder
|
4
|
+
include Signable
|
5
|
+
|
6
|
+
# this is an abstract base class.
|
7
|
+
def build
|
8
|
+
raise "#{self.class} must implement build method"
|
9
|
+
end
|
10
|
+
|
11
|
+
def reference_id
|
12
|
+
UUID.generate
|
13
|
+
end
|
14
|
+
|
15
|
+
def digest
|
16
|
+
algorithm.hexdigest raw
|
17
|
+
end
|
18
|
+
|
19
|
+
def encoded
|
20
|
+
@encoded ||= encode
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw
|
24
|
+
build
|
25
|
+
end
|
26
|
+
|
27
|
+
def encode
|
28
|
+
Base64.strict_encode64(raw)
|
29
|
+
end
|
30
|
+
private :encode
|
31
|
+
|
32
|
+
def response_id_string
|
33
|
+
"_#{response_id}"
|
34
|
+
end
|
35
|
+
private :response_id_string
|
36
|
+
|
37
|
+
def now_iso
|
38
|
+
Time.now.utc.iso8601
|
39
|
+
end
|
40
|
+
private :now_iso
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'saml_idp/logout_builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutRequestBuilder < LogoutBuilder
|
4
|
+
attr_accessor :response_id
|
5
|
+
attr_accessor :issuer_uri
|
6
|
+
attr_accessor :saml_slo_url
|
7
|
+
attr_accessor :name_id
|
8
|
+
attr_accessor :algorithm
|
9
|
+
|
10
|
+
def initialize(response_id, issuer_uri, saml_slo_url, name_id, algorithm)
|
11
|
+
self.response_id = response_id
|
12
|
+
self.issuer_uri = issuer_uri
|
13
|
+
self.saml_slo_url = saml_slo_url
|
14
|
+
self.name_id = name_id
|
15
|
+
self.algorithm = algorithm
|
16
|
+
end
|
17
|
+
|
18
|
+
def build
|
19
|
+
builder = Builder::XmlMarkup.new
|
20
|
+
builder.LogoutRequest ID: response_id_string,
|
21
|
+
Version: "2.0",
|
22
|
+
IssueInstant: now_iso,
|
23
|
+
Destination: saml_slo_url,
|
24
|
+
"xmlns" => Saml::XML::Namespaces::PROTOCOL do |request|
|
25
|
+
request.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
26
|
+
sign request
|
27
|
+
request.NameID name_id, xmlns: Saml::XML::Namespaces::ASSERTION,
|
28
|
+
Format: Saml::XML::Namespaces::Formats::NameId::PERSISTENT
|
29
|
+
request.SessionIndex response_id_string
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private :build
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'saml_idp/logout_builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutResponseBuilder < LogoutBuilder
|
4
|
+
attr_accessor :response_id
|
5
|
+
attr_accessor :issuer_uri
|
6
|
+
attr_accessor :saml_slo_url
|
7
|
+
attr_accessor :saml_request_id
|
8
|
+
attr_accessor :algorithm
|
9
|
+
|
10
|
+
def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
|
11
|
+
self.response_id = response_id
|
12
|
+
self.issuer_uri = issuer_uri
|
13
|
+
self.saml_slo_url = saml_slo_url
|
14
|
+
self.saml_request_id = saml_request_id
|
15
|
+
self.algorithm = algorithm
|
16
|
+
end
|
17
|
+
|
18
|
+
def build
|
19
|
+
builder = Builder::XmlMarkup.new
|
20
|
+
builder.LogoutResponse ID: response_id_string,
|
21
|
+
Version: "2.0",
|
22
|
+
IssueInstant: now_iso,
|
23
|
+
Destination: saml_slo_url,
|
24
|
+
InResponseTo: saml_request_id,
|
25
|
+
xmlns: Saml::XML::Namespaces::PROTOCOL do |response|
|
26
|
+
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
27
|
+
sign response
|
28
|
+
response.Status xmlns: Saml::XML::Namespaces::PROTOCOL do |status|
|
29
|
+
status.StatusCode Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private :build
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'saml_idp/name_id_formatter'
|
2
|
+
require 'saml_idp/attribute_decorator'
|
3
|
+
require 'saml_idp/algorithmable'
|
4
|
+
require 'saml_idp/signable'
|
5
|
+
module SamlIdp
|
6
|
+
class MetadataBuilder
|
7
|
+
include Algorithmable
|
8
|
+
include Signable
|
9
|
+
attr_accessor :configurator
|
10
|
+
|
11
|
+
def initialize(configurator = SamlIdp.config)
|
12
|
+
self.configurator = configurator
|
13
|
+
end
|
14
|
+
|
15
|
+
def fresh
|
16
|
+
builder = Builder::XmlMarkup.new
|
17
|
+
generated_reference_id do
|
18
|
+
builder.EntityDescriptor ID: reference_string,
|
19
|
+
xmlns: Saml::XML::Namespaces::METADATA,
|
20
|
+
"xmlns:saml" => Saml::XML::Namespaces::ASSERTION,
|
21
|
+
"xmlns:ds" => Saml::XML::Namespaces::SIGNATURE,
|
22
|
+
entityID: entity_id do |entity|
|
23
|
+
sign entity
|
24
|
+
|
25
|
+
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
26
|
+
build_key_descriptor descriptor
|
27
|
+
descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
28
|
+
Location: single_logout_service_post_location
|
29
|
+
build_name_id_formats descriptor
|
30
|
+
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
31
|
+
Location: single_service_post_location
|
32
|
+
build_attribute descriptor
|
33
|
+
end
|
34
|
+
|
35
|
+
entity.AttributeAuthorityDescriptor protocolSupportEnumeration: protocol_enumeration do |authority_descriptor|
|
36
|
+
build_key_descriptor authority_descriptor
|
37
|
+
build_organization authority_descriptor
|
38
|
+
build_contact authority_descriptor
|
39
|
+
authority_descriptor.AttributeService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
40
|
+
Location: attribute_service_location
|
41
|
+
build_name_id_formats authority_descriptor
|
42
|
+
build_attribute authority_descriptor
|
43
|
+
end
|
44
|
+
|
45
|
+
build_organization entity
|
46
|
+
build_contact entity
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :raw, :fresh
|
51
|
+
|
52
|
+
def build_key_descriptor(el)
|
53
|
+
el.KeyDescriptor use: "signing" do |key_descriptor|
|
54
|
+
key_descriptor.KeyInfo xmlns: Saml::XML::Namespaces::SIGNATURE do |key_info|
|
55
|
+
key_info.X509Data do |x509|
|
56
|
+
x509.X509Certificate x509_certificate
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
private :build_key_descriptor
|
62
|
+
|
63
|
+
def build_name_id_formats(el)
|
64
|
+
name_id_formats.each do |format|
|
65
|
+
el.NameIDFormat format
|
66
|
+
end
|
67
|
+
end
|
68
|
+
private :build_name_id_formats
|
69
|
+
|
70
|
+
def build_attribute(el)
|
71
|
+
attributes.each do |attribute|
|
72
|
+
el.tag! "saml:Attribute",
|
73
|
+
NameFormat: attribute.name_format,
|
74
|
+
Name: attribute.name,
|
75
|
+
FriendlyName: attribute.friendly_name do |attribute_xml|
|
76
|
+
attribute.values.each do |value|
|
77
|
+
attribute_xml.tag! "saml:AttributeValue", value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private :build_attribute
|
83
|
+
|
84
|
+
def build_organization(el)
|
85
|
+
el.Organization do |organization|
|
86
|
+
organization.OrganizationName organization_name, "xml:lang" => "en"
|
87
|
+
organization.OrganizationDisplayName organization_name, "xml:lang" => "en"
|
88
|
+
organization.OrganizationURL organization_url, "xml:lang" => "en"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private :build_organization
|
92
|
+
|
93
|
+
def build_contact(el)
|
94
|
+
el.ContactPerson contactType: "technical" do |contact|
|
95
|
+
%w[company given_name sur_name telephone mail_to_string].each do |section|
|
96
|
+
section_value = technical_contact.public_send(section)
|
97
|
+
contact.Company section_value if section_value.present?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
private :build_contact
|
102
|
+
|
103
|
+
def reference_string
|
104
|
+
"_#{reference_id}"
|
105
|
+
end
|
106
|
+
private :reference_string
|
107
|
+
|
108
|
+
def entity_id
|
109
|
+
configurator.entity_id.presence || configurator.base_saml_location
|
110
|
+
end
|
111
|
+
private :entity_id
|
112
|
+
|
113
|
+
def protocol_enumeration
|
114
|
+
Saml::XML::Namespaces::PROTOCOL
|
115
|
+
end
|
116
|
+
private :protocol_enumeration
|
117
|
+
|
118
|
+
def attributes
|
119
|
+
@attributes ||= configurator.attributes.inject([]) do |list, (key, opts)|
|
120
|
+
opts[:friendly_name] = key
|
121
|
+
list << AttributeDecorator.new(opts)
|
122
|
+
list
|
123
|
+
end
|
124
|
+
end
|
125
|
+
private :attributes
|
126
|
+
|
127
|
+
def name_id_formats
|
128
|
+
@name_id_formats ||= NameIdFormatter.new(configurator.name_id.formats).all
|
129
|
+
end
|
130
|
+
private :name_id_formats
|
131
|
+
|
132
|
+
def raw_algorithm
|
133
|
+
configurator.algorithm
|
134
|
+
end
|
135
|
+
private :raw_algorithm
|
136
|
+
|
137
|
+
def x509_certificate
|
138
|
+
SamlIdp.config.x509_certificate
|
139
|
+
.to_s
|
140
|
+
.gsub(/-----BEGIN CERTIFICATE-----/,"")
|
141
|
+
.gsub(/-----END CERTIFICATE-----/,"")
|
142
|
+
.gsub(/\n/, "")
|
143
|
+
end
|
144
|
+
|
145
|
+
%w[
|
146
|
+
support_email
|
147
|
+
organization_name
|
148
|
+
organization_url
|
149
|
+
attribute_service_location
|
150
|
+
single_service_post_location
|
151
|
+
single_logout_service_post_location
|
152
|
+
technical_contact
|
153
|
+
].each do |delegatable|
|
154
|
+
define_method(delegatable) do
|
155
|
+
configurator.public_send delegatable
|
156
|
+
end
|
157
|
+
private delegatable
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|