saml2 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|