saml-kit 0.1.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/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/saml-kit-decode-http-redirect +7 -0
- data/lib/saml/kit.rb +60 -0
- data/lib/saml/kit/authentication_request.rb +78 -0
- data/lib/saml/kit/binding.rb +40 -0
- data/lib/saml/kit/configuration.rb +36 -0
- data/lib/saml/kit/default_registry.rb +49 -0
- data/lib/saml/kit/document.rb +96 -0
- data/lib/saml/kit/fingerprint.rb +40 -0
- data/lib/saml/kit/http_post_binding.rb +27 -0
- data/lib/saml/kit/http_redirect_binding.rb +58 -0
- data/lib/saml/kit/identity_provider_metadata.rb +122 -0
- data/lib/saml/kit/invalid_document.rb +13 -0
- data/lib/saml/kit/locales/en.yml +25 -0
- data/lib/saml/kit/logout_request.rb +78 -0
- data/lib/saml/kit/logout_response.rb +63 -0
- data/lib/saml/kit/metadata.rb +171 -0
- data/lib/saml/kit/namespaces.rb +47 -0
- data/lib/saml/kit/requestable.rb +14 -0
- data/lib/saml/kit/respondable.rb +35 -0
- data/lib/saml/kit/response.rb +197 -0
- data/lib/saml/kit/self_signed_certificate.rb +30 -0
- data/lib/saml/kit/serializable.rb +31 -0
- data/lib/saml/kit/service_provider_metadata.rb +99 -0
- data/lib/saml/kit/signature.rb +75 -0
- data/lib/saml/kit/trustable.rb +62 -0
- data/lib/saml/kit/url_builder.rb +38 -0
- data/lib/saml/kit/version.rb +5 -0
- data/lib/saml/kit/xml.rb +57 -0
- data/lib/saml/kit/xsd/MetadataExchange.xsd +95 -0
- data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-secext-1.0.xsd +196 -0
- data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-utility-1.0.xsd +95 -0
- data/lib/saml/kit/xsd/saml-schema-assertion-2.0.xsd +283 -0
- data/lib/saml/kit/xsd/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/saml/kit/xsd/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/saml/kit/xsd/saml-schema-metadata-2.0.xsd +335 -0
- data/lib/saml/kit/xsd/saml-schema-protocol-2.0.xsd +302 -0
- data/lib/saml/kit/xsd/sstc-metadata-attr.xsd +35 -0
- data/lib/saml/kit/xsd/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/saml/kit/xsd/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/saml/kit/xsd/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/saml/kit/xsd/ws-addr.xsd +120 -0
- data/lib/saml/kit/xsd/ws-authorization.xsd +145 -0
- data/lib/saml/kit/xsd/ws-federation.xsd +471 -0
- data/lib/saml/kit/xsd/ws-securitypolicy-1.2.xsd +900 -0
- data/lib/saml/kit/xsd/xenc-schema.xsd +136 -0
- data/lib/saml/kit/xsd/xml.xsd +287 -0
- data/lib/saml/kit/xsd/xmldsig-core-schema.xsd +309 -0
- data/lib/saml/kit/xsd_validatable.rb +19 -0
- data/saml-kit.gemspec +35 -0
- metadata +243 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
module Namespaces
|
4
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
5
|
+
ATTR_SPLAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
|
6
|
+
BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
|
7
|
+
BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
8
|
+
EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
9
|
+
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
10
|
+
HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'
|
11
|
+
HTTP_POST = POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
12
|
+
HTTP_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
13
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
14
|
+
PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
15
|
+
PASSWORD_PROTECTED = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
16
|
+
PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
17
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
18
|
+
REQUESTER_ERROR = "urn:oasis:names:tc:SAML:2.0:status:Requester"
|
19
|
+
RESPONDER_ERROR = "urn:oasis:names:tc:SAML:2.0:status:Responder"
|
20
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
21
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
22
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
23
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
24
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
25
|
+
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
26
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
27
|
+
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
|
28
|
+
SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
29
|
+
TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
30
|
+
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:consent:unspecified"
|
31
|
+
UNSPECIFIED_NAMEID = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
32
|
+
URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
33
|
+
VERSION_MISMATCH_ERROR = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"
|
34
|
+
XMLDSIG = "http://www.w3.org/2000/09/xmldsig#"
|
35
|
+
|
36
|
+
def self.binding_for(binding)
|
37
|
+
if :post == binding
|
38
|
+
Namespaces::HTTP_POST
|
39
|
+
elsif :http_redirect == binding
|
40
|
+
Namespaces::HTTP_REDIRECT
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
module Respondable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
attr_reader :request_id
|
6
|
+
|
7
|
+
included do
|
8
|
+
validates_inclusion_of :status_code, in: [Namespaces::SUCCESS]
|
9
|
+
validate :must_match_request_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def query_string_parameter
|
13
|
+
'SAMLResponse'
|
14
|
+
end
|
15
|
+
|
16
|
+
def status_code
|
17
|
+
to_h.fetch(name, {}).fetch('Status', {}).fetch('StatusCode', {}).fetch('Value', nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
def in_response_to
|
21
|
+
to_h.fetch(name, {}).fetch('InResponseTo', nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def must_match_request_id
|
27
|
+
return if request_id.nil?
|
28
|
+
|
29
|
+
if in_response_to != request_id
|
30
|
+
errors[:in_response_to] << error_message(:invalid_response_to)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class Response < Document
|
4
|
+
include Respondable
|
5
|
+
|
6
|
+
validate :must_be_active_session
|
7
|
+
validate :must_match_issuer
|
8
|
+
|
9
|
+
def initialize(xml, request_id: nil)
|
10
|
+
@request_id = request_id
|
11
|
+
super(xml, name: "Response")
|
12
|
+
end
|
13
|
+
|
14
|
+
def name_id
|
15
|
+
to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Subject', {}).fetch('NameID', nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
attributes[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def attributes
|
23
|
+
@attributes ||= Hash[to_h.fetch(name, {}).fetch('Assertion', {}).fetch('AttributeStatement', {}).fetch('Attribute', []).map do |item|
|
24
|
+
[item['Name'].to_sym, item['AttributeValue']]
|
25
|
+
end].with_indifferent_access
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def started_at
|
30
|
+
parse_date(to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Conditions', {}).fetch('NotBefore', nil))
|
31
|
+
end
|
32
|
+
|
33
|
+
def expired_at
|
34
|
+
parse_date(to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
|
35
|
+
end
|
36
|
+
|
37
|
+
def expired?
|
38
|
+
Time.current > expired_at
|
39
|
+
end
|
40
|
+
|
41
|
+
def active?
|
42
|
+
Time.current > started_at && !expired?
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def must_be_active_session
|
48
|
+
return unless expected_type?
|
49
|
+
errors[:base] << error_message(:expired) unless active?
|
50
|
+
end
|
51
|
+
|
52
|
+
def must_match_issuer
|
53
|
+
return unless expected_type?
|
54
|
+
|
55
|
+
unless audiences.include?(Saml::Kit.configuration.issuer)
|
56
|
+
errors[:audience] << error_message(:must_match_issuer)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def audiences
|
61
|
+
Array(to_h[name]['Assertion']['Conditions']['AudienceRestriction']['Audience'])
|
62
|
+
rescue => error
|
63
|
+
Saml::Kit.logger.error(error)
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_date(value)
|
68
|
+
DateTime.parse(value)
|
69
|
+
rescue => error
|
70
|
+
Saml::Kit.logger.error(error)
|
71
|
+
Time.at(0).to_datetime
|
72
|
+
end
|
73
|
+
|
74
|
+
class Builder
|
75
|
+
attr_reader :user, :request
|
76
|
+
attr_accessor :id, :reference_id, :now
|
77
|
+
attr_accessor :version, :status_code
|
78
|
+
attr_accessor :issuer, :sign, :destination
|
79
|
+
|
80
|
+
def initialize(user, request)
|
81
|
+
@user = user
|
82
|
+
@request = request
|
83
|
+
@id = SecureRandom.uuid
|
84
|
+
@reference_id = SecureRandom.uuid
|
85
|
+
@now = Time.now.utc
|
86
|
+
@version = "2.0"
|
87
|
+
@status_code = Namespaces::SUCCESS
|
88
|
+
@issuer = configuration.issuer
|
89
|
+
@destination = request.acs_url
|
90
|
+
@sign = want_assertions_signed
|
91
|
+
end
|
92
|
+
|
93
|
+
def want_assertions_signed
|
94
|
+
request.provider.want_assertions_signed
|
95
|
+
rescue => error
|
96
|
+
Saml::Kit.logger.error(error)
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_xml
|
101
|
+
Signature.sign(id, sign: sign) do |xml, signature|
|
102
|
+
xml.Response response_options do
|
103
|
+
xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
|
104
|
+
signature.template(xml)
|
105
|
+
xml.Status do
|
106
|
+
xml.StatusCode Value: status_code
|
107
|
+
end
|
108
|
+
xml.Assertion(assertion_options) do
|
109
|
+
xml.Issuer issuer
|
110
|
+
xml.Subject do
|
111
|
+
xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
|
112
|
+
xml.SubjectConfirmation Method: Namespaces::BEARER do
|
113
|
+
xml.SubjectConfirmationData "", subject_confirmation_data_options
|
114
|
+
end
|
115
|
+
end
|
116
|
+
xml.Conditions conditions_options do
|
117
|
+
xml.AudienceRestriction do
|
118
|
+
xml.Audience request.issuer
|
119
|
+
end
|
120
|
+
end
|
121
|
+
xml.AuthnStatement authn_statement_options do
|
122
|
+
xml.AuthnContext do
|
123
|
+
xml.AuthnContextClassRef Namespaces::PASSWORD
|
124
|
+
end
|
125
|
+
end
|
126
|
+
assertion_attributes = user.assertion_attributes_for(request)
|
127
|
+
if assertion_attributes.any?
|
128
|
+
xml.AttributeStatement do
|
129
|
+
assertion_attributes.each do |key, value|
|
130
|
+
xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
|
131
|
+
xml.AttributeValue value.to_s
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def build
|
142
|
+
Response.new(to_xml, request_id: request.id)
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def configuration
|
148
|
+
Saml::Kit.configuration
|
149
|
+
end
|
150
|
+
|
151
|
+
def response_options
|
152
|
+
{
|
153
|
+
ID: id.present? ? "_#{id}" : nil,
|
154
|
+
Version: version,
|
155
|
+
IssueInstant: now.iso8601,
|
156
|
+
Destination: destination,
|
157
|
+
Consent: Namespaces::UNSPECIFIED,
|
158
|
+
InResponseTo: request.id,
|
159
|
+
xmlns: Namespaces::PROTOCOL,
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def assertion_options
|
164
|
+
{
|
165
|
+
ID: "_#{reference_id}",
|
166
|
+
IssueInstant: now.iso8601,
|
167
|
+
Version: "2.0",
|
168
|
+
xmlns: Namespaces::ASSERTION,
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def subject_confirmation_data_options
|
173
|
+
{
|
174
|
+
InResponseTo: request.id,
|
175
|
+
NotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
176
|
+
Recipient: request.acs_url,
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def conditions_options
|
181
|
+
{
|
182
|
+
NotBefore: now.utc.iso8601,
|
183
|
+
NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
def authn_statement_options
|
188
|
+
{
|
189
|
+
AuthnInstant: now.iso8601,
|
190
|
+
SessionIndex: assertion_options[:ID],
|
191
|
+
SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
192
|
+
}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class SelfSignedCertificate
|
4
|
+
def initialize(password)
|
5
|
+
@password = password
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
10
|
+
public_key = rsa_key.public_key
|
11
|
+
certificate = OpenSSL::X509::Certificate.new
|
12
|
+
certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/C=CA/ST=Alberta/L=Calgary/O=Xsig/OU=Xsig/CN=Xsig")
|
13
|
+
certificate.not_before = DateTime.now.beginning_of_day
|
14
|
+
certificate.not_after = 1.year.from_now.end_of_day
|
15
|
+
certificate.public_key = public_key
|
16
|
+
certificate.serial = 0x0
|
17
|
+
certificate.version = 2
|
18
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
19
|
+
factory.subject_certificate = factory.issuer_certificate = certificate
|
20
|
+
certificate.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
|
21
|
+
certificate.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
|
22
|
+
certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
|
23
|
+
[
|
24
|
+
certificate.to_pem,
|
25
|
+
rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), @password)
|
26
|
+
]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
module Serializable
|
4
|
+
def decode(value)
|
5
|
+
Base64.decode64(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def encode(value)
|
9
|
+
Base64.strict_encode64(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def inflate(value)
|
13
|
+
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
14
|
+
inflater.inflate(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
# drop header and checksum as per spec.
|
18
|
+
def deflate(value, level: Zlib::BEST_COMPRESSION)
|
19
|
+
Zlib::Deflate.deflate(value, level)[2..-5]
|
20
|
+
end
|
21
|
+
|
22
|
+
def unescape(value)
|
23
|
+
CGI.unescape(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def escape(value)
|
27
|
+
CGI.escape(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class ServiceProviderMetadata < Metadata
|
4
|
+
def initialize(xml)
|
5
|
+
super("SPSSODescriptor", xml)
|
6
|
+
end
|
7
|
+
|
8
|
+
def assertion_consumer_services
|
9
|
+
services('AssertionConsumerService')
|
10
|
+
end
|
11
|
+
|
12
|
+
def assertion_consumer_service_for(binding:)
|
13
|
+
service_for(binding: binding, type: 'AssertionConsumerService')
|
14
|
+
end
|
15
|
+
|
16
|
+
def want_assertions_signed
|
17
|
+
attribute = find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
|
18
|
+
attribute.text.downcase == "true"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
class Builder
|
24
|
+
attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
|
25
|
+
attr_accessor :want_assertions_signed
|
26
|
+
|
27
|
+
def initialize(configuration = Saml::Kit.configuration)
|
28
|
+
@id = SecureRandom.uuid
|
29
|
+
@configuration = configuration
|
30
|
+
@entity_id = configuration.issuer
|
31
|
+
@acs_urls = []
|
32
|
+
@logout_urls = []
|
33
|
+
@name_id_formats = [Namespaces::PERSISTENT]
|
34
|
+
@sign = true
|
35
|
+
@want_assertions_signed = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_assertion_consumer_service(url, binding: :post)
|
39
|
+
@acs_urls.push(location: url, binding: Namespaces.binding_for(binding))
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_single_logout_service(url, binding: :post)
|
43
|
+
@logout_urls.push(location: url, binding: Namespaces.binding_for(binding))
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_xml
|
47
|
+
Signature.sign(id, sign: sign) do |xml, signature|
|
48
|
+
xml.instruct!
|
49
|
+
xml.EntityDescriptor entity_descriptor_options do
|
50
|
+
signature.template(xml)
|
51
|
+
xml.SPSSODescriptor descriptor_options do
|
52
|
+
if @configuration.signing_certificate_pem.present?
|
53
|
+
xml.KeyDescriptor use: "signing" do
|
54
|
+
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
55
|
+
xml.X509Data do
|
56
|
+
xml.X509Certificate @configuration.stripped_signing_certificate
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
logout_urls.each do |item|
|
62
|
+
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
63
|
+
end
|
64
|
+
name_id_formats.each do |format|
|
65
|
+
xml.NameIDFormat format
|
66
|
+
end
|
67
|
+
acs_urls.each_with_index do |item, index|
|
68
|
+
xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def build
|
76
|
+
ServiceProviderMetadata.new(to_xml)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def entity_descriptor_options
|
82
|
+
{
|
83
|
+
'xmlns': Namespaces::METADATA,
|
84
|
+
ID: "_#{id}",
|
85
|
+
entityID: entity_id,
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def descriptor_options
|
90
|
+
{
|
91
|
+
AuthnRequestsSigned: sign,
|
92
|
+
WantAssertionsSigned: want_assertions_signed,
|
93
|
+
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|