saml2 2.0.2 → 2.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 +4 -4
- data/lib/saml2.rb +2 -0
- data/lib/saml2/assertion.rb +6 -0
- data/lib/saml2/attribute.rb +45 -13
- data/lib/saml2/attribute/x500.rb +32 -19
- data/lib/saml2/attribute_consuming_service.rb +52 -4
- data/lib/saml2/authn_request.rb +39 -3
- data/lib/saml2/authn_statement.rb +23 -11
- data/lib/saml2/base.rb +36 -0
- data/lib/saml2/bindings.rb +3 -1
- data/lib/saml2/bindings/http_post.rb +17 -1
- data/lib/saml2/bindings/http_redirect.rb +54 -9
- data/lib/saml2/conditions.rb +43 -16
- data/lib/saml2/contact.rb +17 -6
- data/lib/saml2/endpoint.rb +13 -0
- data/lib/saml2/engine.rb +2 -0
- data/lib/saml2/entity.rb +20 -0
- data/lib/saml2/identity_provider.rb +11 -1
- data/lib/saml2/indexed_object.rb +13 -3
- data/lib/saml2/key.rb +89 -32
- data/lib/saml2/localized_name.rb +8 -0
- data/lib/saml2/logout_request.rb +12 -3
- data/lib/saml2/logout_response.rb +9 -0
- data/lib/saml2/message.rb +38 -7
- data/lib/saml2/name_id.rb +42 -16
- data/lib/saml2/namespaces.rb +10 -8
- data/lib/saml2/organization.rb +5 -0
- data/lib/saml2/organization_and_contacts.rb +5 -0
- data/lib/saml2/request.rb +3 -0
- data/lib/saml2/requested_authn_context.rb +7 -1
- data/lib/saml2/response.rb +20 -2
- data/lib/saml2/role.rb +12 -2
- data/lib/saml2/schemas.rb +2 -0
- data/lib/saml2/service_provider.rb +6 -0
- data/lib/saml2/signable.rb +32 -2
- data/lib/saml2/sso.rb +7 -0
- data/lib/saml2/status.rb +8 -1
- data/lib/saml2/status_response.rb +7 -1
- data/lib/saml2/subject.rb +22 -5
- data/lib/saml2/version.rb +3 -1
- data/spec/lib/bindings/http_redirect_spec.rb +23 -2
- data/spec/lib/conditions_spec.rb +10 -11
- data/spec/lib/identity_provider_spec.rb +1 -1
- data/spec/lib/service_provider_spec.rb +7 -2
- metadata +5 -5
@@ -1,22 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/base'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class AuthnStatement < Base
|
5
7
|
module Classes
|
6
|
-
INTERNET_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol"
|
7
|
-
INTERNET_PROTOCOL_PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword"
|
8
|
-
KERBEROS = "urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos"
|
9
|
-
PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
10
|
-
PASSWORD_PROTECTED_TRANSPORT = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
11
|
-
PREVIOUS_SESSION = "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession"
|
12
|
-
SMARTCARD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard"
|
13
|
-
SMARTCARD_PKI = "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI"
|
14
|
-
TLS_CLIENT = "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient"
|
15
|
-
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
|
8
|
+
INTERNET_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol" # IP address
|
9
|
+
INTERNET_PROTOCOL_PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword" # IP address, as well as username/password
|
10
|
+
KERBEROS = "urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos"
|
11
|
+
PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password" # username/password, NOT over SSL
|
12
|
+
PASSWORD_PROTECTED_TRANSPORT = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" # username/password over SSL
|
13
|
+
PREVIOUS_SESSION = "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession" # remember me
|
14
|
+
SMARTCARD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard"
|
15
|
+
SMARTCARD_PKI = "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI" # smartcard with a private key on it
|
16
|
+
TLS_CLIENT = "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient" # SSL client certificate
|
17
|
+
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
|
16
18
|
end
|
17
19
|
|
18
|
-
|
20
|
+
# @return [Time]
|
21
|
+
attr_accessor :authn_instant
|
22
|
+
# One of the values in {Classes}.
|
23
|
+
# @return [String, nil]
|
24
|
+
attr_accessor :authn_context_class_ref
|
25
|
+
# @return [String, nil]
|
26
|
+
attr_accessor :session_index
|
27
|
+
# @return [Time, nil]
|
28
|
+
attr_accessor :session_not_on_or_after
|
19
29
|
|
30
|
+
# (see Base#from_xml)
|
20
31
|
def from_xml(node)
|
21
32
|
super
|
22
33
|
@authn_instant = Time.parse(node['AuthnInstant'])
|
@@ -25,6 +36,7 @@ module SAML2
|
|
25
36
|
@authn_context_class_ref = node.at_xpath('saml:AuthnContext/saml:AuthnContextClassRef', Namespaces::ALL)&.content&.strip
|
26
37
|
end
|
27
38
|
|
39
|
+
# (see Base#build)
|
28
40
|
def build(builder)
|
29
41
|
builder['saml'].AuthnStatement('AuthnInstant' => authn_instant.iso8601) do |authn_statement|
|
30
42
|
authn_statement.parent['SessionIndex'] = session_index if session_index
|
data/lib/saml2/base.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/namespaces'
|
2
4
|
|
3
5
|
module SAML2
|
6
|
+
# @abstract
|
4
7
|
class Base
|
8
|
+
# Create an appropriate object to represent the given XML element.
|
9
|
+
#
|
10
|
+
# @param node [Nokogiri::XML::Element, nil]
|
11
|
+
# @return [Base, nil]
|
5
12
|
def self.from_xml(node)
|
6
13
|
return nil unless node
|
7
14
|
result = new
|
@@ -9,16 +16,31 @@ module SAML2
|
|
9
16
|
result
|
10
17
|
end
|
11
18
|
|
19
|
+
# @return [Nokogiri::XML::Element]
|
12
20
|
attr_reader :xml
|
13
21
|
|
14
22
|
def initialize
|
15
23
|
@pretty = true
|
16
24
|
end
|
17
25
|
|
26
|
+
# Parse an XML element into this object.
|
27
|
+
#
|
28
|
+
# @param node [Nokogiri::XML::Element]
|
29
|
+
# @return [void]
|
18
30
|
def from_xml(node)
|
19
31
|
@xml = node
|
20
32
|
end
|
21
33
|
|
34
|
+
# Returns the XML of this object as a string.
|
35
|
+
#
|
36
|
+
# If this object came from parsing XML, it will always return it with the
|
37
|
+
# same formatting as it was parsed.
|
38
|
+
#
|
39
|
+
# @param pretty optional [true, false, nil]
|
40
|
+
# +true+ forces it to format it for easy reading. +nil+ will prefer to
|
41
|
+
# format it pretty, but won't if e.g. it has been signed, and pretty
|
42
|
+
# formatting would break the signature.
|
43
|
+
# @return [String]
|
22
44
|
def to_s(pretty: nil)
|
23
45
|
pretty = @pretty if pretty.nil?
|
24
46
|
if xml
|
@@ -31,10 +53,19 @@ module SAML2
|
|
31
53
|
end
|
32
54
|
end
|
33
55
|
|
56
|
+
# Inspect the object
|
57
|
+
#
|
58
|
+
# The +@xml+ instance variable is omitted, keeping this useful. However, if
|
59
|
+
# an object lazily parses sub-objects, then their instance variables will
|
60
|
+
# not be created until their attribute is accessed.
|
61
|
+
# @return [String]
|
34
62
|
def inspect
|
35
63
|
"#<#{self.class.name} #{instance_variables.map { |iv| next if iv == :@xml; "#{iv}=#{instance_variable_get(iv).inspect}" }.compact.join(", ") }>"
|
36
64
|
end
|
37
65
|
|
66
|
+
# Serialize this object to XML
|
67
|
+
#
|
68
|
+
# @return [Nokogiri::XML::Document]
|
38
69
|
def to_xml
|
39
70
|
unless instance_variable_defined?(:@document)
|
40
71
|
builder = Nokogiri::XML::Builder.new
|
@@ -47,6 +78,10 @@ module SAML2
|
|
47
78
|
@document
|
48
79
|
end
|
49
80
|
|
81
|
+
# Serialize this object to XML, as part of a larger document
|
82
|
+
#
|
83
|
+
# @param builder [Nokogiri::XML::Builder] The builder helper object to serialize to.
|
84
|
+
# @return [void]
|
50
85
|
def build(builder)
|
51
86
|
end
|
52
87
|
|
@@ -56,6 +91,7 @@ module SAML2
|
|
56
91
|
end
|
57
92
|
end
|
58
93
|
|
94
|
+
|
59
95
|
def self.load_object_array(node, element, klass = nil)
|
60
96
|
node.xpath(element, Namespaces::ALL).map do |element_node|
|
61
97
|
if klass.nil?
|
data/lib/saml2/bindings.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
module Bindings
|
5
7
|
module HTTP_POST
|
6
|
-
URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
8
|
+
URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
7
9
|
|
8
10
|
class << self
|
11
|
+
# Decode and parse a Base64 encoded SAML message.
|
12
|
+
#
|
13
|
+
# @param post_params [Hash<String => String>]
|
14
|
+
# The POST params. Will check for both +SAMLRequest+ and
|
15
|
+
# +SAMLResponse+ params.
|
16
|
+
# @return [[Message, String]]
|
17
|
+
# The Message and the RelayState.
|
9
18
|
def decode(post_params)
|
10
19
|
base64 = post_params['SAMLRequest'] || post_params['SAMLResponse']
|
11
20
|
raise MissingMessage unless base64
|
@@ -22,6 +31,13 @@ module SAML2
|
|
22
31
|
[message, post_params['RelayState']]
|
23
32
|
end
|
24
33
|
|
34
|
+
# Encode a SAML message into Base64 POST params.
|
35
|
+
#
|
36
|
+
# @param message [Message]
|
37
|
+
# @param relay_state optional [String]
|
38
|
+
# @return [Hash<String => String>]
|
39
|
+
# The POST params, including +RelayState+, and +SAMLRequest+ vs.
|
40
|
+
# +SAMLResponse+ chosen appropriately.
|
25
41
|
def encode(message, relay_state: nil)
|
26
42
|
xml = message.to_s(pretty: false)
|
27
43
|
key = message.is_a?(Request) ? 'SAMLRequest' : 'SAMLResponse'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
require 'uri'
|
3
5
|
require 'zlib'
|
@@ -8,16 +10,39 @@ require 'saml2/message'
|
|
8
10
|
module SAML2
|
9
11
|
module Bindings
|
10
12
|
module HTTPRedirect
|
11
|
-
URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
13
|
+
URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
12
14
|
|
13
15
|
module SigAlgs
|
14
|
-
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
15
|
-
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
16
|
+
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
17
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
18
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
16
19
|
|
17
|
-
RECOGNIZED = [DSA_SHA1, RSA_SHA1].freeze
|
20
|
+
RECOGNIZED = [DSA_SHA1, RSA_SHA1, RSA_SHA256].freeze
|
18
21
|
end
|
19
22
|
|
20
23
|
class << self
|
24
|
+
# Decode, validate signature, and parse a compressed and Base64 encoded
|
25
|
+
# SAML message.
|
26
|
+
#
|
27
|
+
# A signature, if present, will be verified only if +public_key+ is
|
28
|
+
# passed.
|
29
|
+
#
|
30
|
+
# @param url [String]
|
31
|
+
# The full URL to decode. Will check for both +SAMLRequest+ and
|
32
|
+
# +SAMLResponse+ params.
|
33
|
+
# @param public_key optional [Array<OpenSSL::PKey>, OpenSSL::PKey, Proc]
|
34
|
+
# Keys to use to check the signature. If a +Proc+ is provided, it is
|
35
|
+
# called with the parsed {Message}, and the +SigAlg+ in order for the
|
36
|
+
# caller to find an appropriate key based on the {Message}'s issuer.
|
37
|
+
# @param public_key_used optional [Proc]
|
38
|
+
# Is called with the actual key that was used to validate the
|
39
|
+
# signature.
|
40
|
+
# @return [[Message, String]]
|
41
|
+
# The Message and the RelayState.
|
42
|
+
# @raise [UnsignedMessage] If a public_key is provided, but the message
|
43
|
+
# is not signed.
|
44
|
+
# @yield [message, sig_alg]
|
45
|
+
# The same as a +Proc+ provided to +public_key+. Deprecated.
|
21
46
|
def decode(url, public_key: nil, public_key_used: nil)
|
22
47
|
uri = begin
|
23
48
|
URI.parse(url)
|
@@ -51,7 +76,7 @@ module SAML2
|
|
51
76
|
end
|
52
77
|
|
53
78
|
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
54
|
-
xml =
|
79
|
+
xml = String.new
|
55
80
|
begin
|
56
81
|
# do it in 1K slices, so we can protect against bombs
|
57
82
|
(0..deflated.bytesize / 1024).each do |i|
|
@@ -69,6 +94,7 @@ module SAML2
|
|
69
94
|
# if a block is provided, it's to fetch the proper certificate
|
70
95
|
# based on the contents of the message
|
71
96
|
public_key ||= yield(message, sig_alg) if block_given?
|
97
|
+
public_key = public_key.call(message, sig_alg) if public_key.is_a?(Proc)
|
72
98
|
if public_key
|
73
99
|
raise UnsignedMessage unless signature
|
74
100
|
raise UnsupportedSignatureAlgorithm unless SigAlgs::RECOGNIZED.include?(sig_alg)
|
@@ -86,7 +112,8 @@ module SAML2
|
|
86
112
|
valid_signature = false
|
87
113
|
# there could be multiple certificates to try
|
88
114
|
Array(public_key).each do |key|
|
89
|
-
|
115
|
+
hash = (sig_alg == SigAlgs::RSA_SHA256 ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
|
116
|
+
if key.verify(hash.new, signature, base_string)
|
90
117
|
# notify the caller which certificate was used
|
91
118
|
public_key_used&.call(key)
|
92
119
|
valid_signature = true
|
@@ -98,7 +125,22 @@ module SAML2
|
|
98
125
|
[message, relay_state]
|
99
126
|
end
|
100
127
|
|
101
|
-
|
128
|
+
# Encode a SAML message into Base64, compressed query params.
|
129
|
+
#
|
130
|
+
# @param message [Message]
|
131
|
+
# Note that the base URI is taken from {Message#destination}.
|
132
|
+
# @param relay_state optional [String]
|
133
|
+
# @param private_key optional [OpenSSL::PKey::RSA]
|
134
|
+
# A key to use to sign the encoded message.
|
135
|
+
# @param sig_alg optional [String]
|
136
|
+
# The signing algorithm to use. Defaults to RSA-SHA1, as it's the
|
137
|
+
# most compatible, and explicitly mentioned in the SAML specs, but
|
138
|
+
# you may want to use RSA-SHA256. Values must come from {SigAlgs}.
|
139
|
+
# @return [String]
|
140
|
+
# The full URI to redirect to, including +RelayState+, and
|
141
|
+
# +SAMLRequest+ vs. +SAMLResponse+ chosen appropriately, and
|
142
|
+
# +Signature+ + +SigAlg+ query params if signing.
|
143
|
+
def encode(message, relay_state: nil, private_key: nil, sig_alg: SigAlgs::RSA_SHA1)
|
102
144
|
result = URI.parse(message.destination)
|
103
145
|
original_query = URI.decode_www_form(result.query) if result.query
|
104
146
|
original_query ||= []
|
@@ -117,9 +159,12 @@ module SAML2
|
|
117
159
|
query << [message.is_a?(Request) ? 'SAMLRequest' : 'SAMLResponse', base64]
|
118
160
|
query << ['RelayState', relay_state] if relay_state
|
119
161
|
if private_key
|
120
|
-
|
162
|
+
raise ArgumentError, "Unsupported signature algorithm #{sig_alg}" unless SigAlgs::RECOGNIZED.include?(sig_alg)
|
163
|
+
|
164
|
+
query << ['SigAlg', sig_alg]
|
121
165
|
base_string = URI.encode_www_form(query)
|
122
|
-
|
166
|
+
hash = (sig_alg == SigAlgs::RSA_SHA256 ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
|
167
|
+
signature = private_key.sign(hash.new, base_string)
|
123
168
|
query << ['Signature', Base64.strict_encode64(signature)]
|
124
169
|
end
|
125
170
|
|
data/lib/saml2/conditions.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/array/wrap'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class Conditions < Array
|
7
|
+
# @return [Time, nil]
|
5
8
|
attr_accessor :not_before, :not_on_or_after
|
9
|
+
# (see Base#xml)
|
6
10
|
attr_reader :xml
|
7
11
|
|
12
|
+
# (see Base.from_xml)
|
8
13
|
def self.from_xml(node)
|
9
14
|
result = new
|
10
15
|
result.from_xml(node)
|
11
16
|
result
|
12
17
|
end
|
13
18
|
|
19
|
+
# (see Base#from_xml)
|
14
20
|
def from_xml(node)
|
15
21
|
@xml = node
|
16
22
|
@not_before = Time.parse(node['NotBefore']) if node['NotBefore']
|
@@ -19,20 +25,30 @@ module SAML2
|
|
19
25
|
replace(node.children.map { |restriction| self.class.const_get(restriction.name, false).from_xml(restriction) })
|
20
26
|
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
# Evaluate these conditions.
|
29
|
+
#
|
30
|
+
# @param now optional [Time]
|
31
|
+
# @param options
|
32
|
+
# Additional options to pass to specific {Condition}s
|
33
|
+
# @return [Boolean, nil]
|
34
|
+
# It's only valid if every sub-condition is completely valid.
|
35
|
+
# If any sub-condition is invalid, the whole statement is invalid.
|
36
|
+
# If the validity can't be determined due to an unsupported condition,
|
37
|
+
# +nil+ will be returned (which is false-ish)
|
38
|
+
def valid?(now: Time.now.utc, **options)
|
39
|
+
options[:now] ||= now
|
40
|
+
return false if not_before && now < not_before
|
41
|
+
return false if not_on_or_after && now >= not_on_or_after
|
42
|
+
|
43
|
+
result = true
|
28
44
|
each do |condition|
|
29
|
-
this_result = condition.valid?(options)
|
45
|
+
this_result = condition.valid?(**options)
|
30
46
|
case this_result
|
31
|
-
when
|
32
|
-
return
|
33
|
-
when
|
34
|
-
result =
|
35
|
-
|
47
|
+
when false
|
48
|
+
return false
|
49
|
+
when nil
|
50
|
+
result = nil
|
51
|
+
when true
|
36
52
|
else
|
37
53
|
raise "unknown validity of #{condition}"
|
38
54
|
end
|
@@ -40,6 +56,7 @@ module SAML2
|
|
40
56
|
result
|
41
57
|
end
|
42
58
|
|
59
|
+
# (see Base#build)
|
43
60
|
def build(builder)
|
44
61
|
builder['saml'].Conditions do |conditions|
|
45
62
|
conditions.parent['NotBefore'] = not_before.iso8601 if not_before
|
@@ -53,31 +70,37 @@ module SAML2
|
|
53
70
|
|
54
71
|
# Any unknown condition
|
55
72
|
class Condition < Base
|
73
|
+
# @return [nil]
|
56
74
|
def valid?(_)
|
57
|
-
|
75
|
+
nil
|
58
76
|
end
|
59
77
|
end
|
60
78
|
|
61
79
|
class AudienceRestriction < Condition
|
62
80
|
attr_writer :audience
|
63
81
|
|
82
|
+
# @param audience [Array<String>]
|
64
83
|
def initialize(audience = [])
|
65
84
|
@audience = audience
|
66
85
|
end
|
67
86
|
|
87
|
+
# (see Base#from_xml)
|
68
88
|
def from_xml(node)
|
69
89
|
super
|
70
90
|
@audience = nil
|
71
91
|
end
|
72
92
|
|
93
|
+
# @return [Array<String>] Allowed audiences
|
73
94
|
def audience
|
74
95
|
@audience ||= load_string_array(xml, 'saml:Audience')
|
75
96
|
end
|
76
97
|
|
77
|
-
|
78
|
-
|
98
|
+
# @param audience [String]
|
99
|
+
def valid?(audience: nil, **_)
|
100
|
+
Array.wrap(self.audience).include?(audience)
|
79
101
|
end
|
80
102
|
|
103
|
+
# (see Base#build)
|
81
104
|
def build(builder)
|
82
105
|
builder['saml'].AudienceRestriction do |audience_restriction|
|
83
106
|
Array.wrap(audience).each do |single_audience|
|
@@ -88,10 +111,14 @@ module SAML2
|
|
88
111
|
end
|
89
112
|
|
90
113
|
class OneTimeUse < Condition
|
114
|
+
# The caller will need to see if this condition exists, and validate it
|
115
|
+
# using their own state store.
|
116
|
+
# @return [true]
|
91
117
|
def valid?(_)
|
92
|
-
|
118
|
+
true
|
93
119
|
end
|
94
120
|
|
121
|
+
# (see Base#build)
|
95
122
|
def build(builder)
|
96
123
|
builder['saml'].OneTimeUse
|
97
124
|
end
|
data/lib/saml2/contact.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/base'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class Contact < Base
|
5
7
|
module Type
|
6
|
-
ADMINISTRATIVE = 'administrative'
|
7
|
-
BILLING = 'billing'
|
8
|
-
OTHER = 'other'
|
9
|
-
SUPPORT = 'support'
|
10
|
-
TECHNICAL = 'technical'
|
8
|
+
ADMINISTRATIVE = 'administrative'
|
9
|
+
BILLING = 'billing'
|
10
|
+
OTHER = 'other'
|
11
|
+
SUPPORT = 'support'
|
12
|
+
TECHNICAL = 'technical'
|
11
13
|
end
|
12
14
|
|
13
|
-
|
15
|
+
# @see Type
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :type
|
18
|
+
# @return [String, nil]
|
19
|
+
attr_accessor :company, :given_name, :surname
|
20
|
+
# @return [Array<String>]
|
21
|
+
attr_accessor :email_addresses, :telephone_numbers
|
14
22
|
|
23
|
+
# @param type [String]
|
15
24
|
def initialize(type = Type::OTHER)
|
16
25
|
@type = type
|
17
26
|
@email_addresses = []
|
18
27
|
@telephone_numbers = []
|
19
28
|
end
|
20
29
|
|
30
|
+
# (see Base#from_xml)
|
21
31
|
def from_xml(node)
|
22
32
|
self.type = node['contactType']
|
23
33
|
company = node.at_xpath('md:Company', Namespaces::ALL)
|
@@ -31,6 +41,7 @@ module SAML2
|
|
31
41
|
self
|
32
42
|
end
|
33
43
|
|
44
|
+
# (see Base#build)
|
34
45
|
def build(builder)
|
35
46
|
builder['md'].ContactPerson('contactType' => type) do |contact_person|
|
36
47
|
contact_person['md'].Company(company) if company
|