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,7 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/status_response'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class LogoutResponse < StatusResponse
|
7
|
+
# @param logout_request [LogoutRequest]
|
8
|
+
# @param sso [SSO]
|
9
|
+
# @param issuer [NameID]
|
10
|
+
# @param status_code [String]
|
11
|
+
# @return [LogoutResponse]
|
5
12
|
def self.respond_to(logout_request, sso, issuer, status_code = Status::SUCCESS)
|
6
13
|
logout_response = new
|
7
14
|
logout_response.issuer = issuer
|
@@ -11,6 +18,8 @@ module SAML2
|
|
11
18
|
logout_response
|
12
19
|
end
|
13
20
|
|
21
|
+
private
|
22
|
+
|
14
23
|
def build(builder)
|
15
24
|
builder['samlp'].LogoutResponse(
|
16
25
|
'xmlns:samlp' => Namespaces::SAMLP,
|
data/lib/saml2/message.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'securerandom'
|
2
4
|
require 'time'
|
3
5
|
|
@@ -37,18 +39,23 @@ module SAML2
|
|
37
39
|
|
38
40
|
# In the SAML Schema, Request and Response don't technically share a common
|
39
41
|
# ancestor, but they have several things in common so it's useful to represent
|
40
|
-
# that
|
42
|
+
# that in this gem as a common base class.
|
43
|
+
# @abstract
|
41
44
|
class Message < Base
|
42
45
|
include Signable
|
43
46
|
|
44
47
|
attr_writer :issuer, :destination
|
45
48
|
|
46
49
|
class << self
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
# Create an appropriate {Message} subclass instance to represent the
|
51
|
+
# given XML element.
|
52
|
+
#
|
53
|
+
# When called on a subclass, it behaves the same as {Base.from_xml}
|
54
|
+
#
|
55
|
+
# @param node [Nokogiri::XML::Element]
|
56
|
+
# @return [Message]
|
57
|
+
# @raise [UnknownMessage] If the element doesn't correspond to a known
|
58
|
+
# SAML message type.
|
52
59
|
def from_xml(node)
|
53
60
|
return super unless self == Message
|
54
61
|
klass = Message.known_messages[node.name]
|
@@ -56,6 +63,13 @@ module SAML2
|
|
56
63
|
klass.from_xml(node)
|
57
64
|
end
|
58
65
|
|
66
|
+
# Parses XML, and returns an appropriate {Message} subclass instance.
|
67
|
+
#
|
68
|
+
# @param xml [String, IO] Anything that can be passed to +Nokogiri::XML+.
|
69
|
+
# @return [Message]
|
70
|
+
# @raise [UnexpectedMessage]
|
71
|
+
# If called on a subclass, will raise if the parsed message does not
|
72
|
+
# match the class is was called on.
|
59
73
|
def parse(xml)
|
60
74
|
result = Message.from_xml(Nokogiri::XML(xml) { |config| config.strict }.root)
|
61
75
|
raise UnexpectedMessage.new("Expected a #{self.name}, but got a #{result.class.name}") unless self == Message || result.class == self
|
@@ -69,6 +83,12 @@ module SAML2
|
|
69
83
|
def known_messages
|
70
84
|
@known_messages ||= {}
|
71
85
|
end
|
86
|
+
|
87
|
+
def inherited(klass)
|
88
|
+
# explicitly keep track of all messages in this base class
|
89
|
+
Message.known_messages[klass.name.sub(/^SAML2::/, '')] = klass
|
90
|
+
end
|
91
|
+
|
72
92
|
end
|
73
93
|
|
74
94
|
def initialize
|
@@ -77,23 +97,30 @@ module SAML2
|
|
77
97
|
@issue_instant = Time.now.utc
|
78
98
|
end
|
79
99
|
|
100
|
+
# (see Base#from_xml)
|
80
101
|
def from_xml(node)
|
81
102
|
super
|
82
103
|
@id = nil
|
83
104
|
@issue_instant = nil
|
84
105
|
end
|
85
106
|
|
107
|
+
# If the XML is valid according to SAML XSDs.
|
108
|
+
# @return [Boolean]
|
86
109
|
def valid_schema?
|
87
110
|
return false unless Schemas.protocol.valid?(xml.document)
|
88
111
|
|
89
112
|
true
|
90
113
|
end
|
91
114
|
|
92
|
-
|
115
|
+
# (see Signable#validate_signature)
|
116
|
+
# @param verification_time
|
117
|
+
# Ignored. The message's {issue_instant} is always used.
|
118
|
+
def validate_signature(fingerprint: nil, cert: nil, verification_time: issue_instant)
|
93
119
|
# verify the signature (certificate's validity) as of the time the message was generated
|
94
120
|
super(fingerprint: fingerprint, cert: cert, verification_time: issue_instant)
|
95
121
|
end
|
96
122
|
|
123
|
+
# (see Signable#sign)
|
97
124
|
def sign(x509_certificate, private_key, algorithm_name = :sha256)
|
98
125
|
super
|
99
126
|
|
@@ -105,14 +132,17 @@ module SAML2
|
|
105
132
|
self
|
106
133
|
end
|
107
134
|
|
135
|
+
# @return [String]
|
108
136
|
def id
|
109
137
|
@id ||= xml['ID']
|
110
138
|
end
|
111
139
|
|
140
|
+
# @return [Time]
|
112
141
|
def issue_instant
|
113
142
|
@issue_instant ||= Time.parse(xml['IssueInstant'])
|
114
143
|
end
|
115
144
|
|
145
|
+
# @return [String, nil]
|
116
146
|
def destination
|
117
147
|
if xml && !instance_variable_defined?(:@destination)
|
118
148
|
@destination = xml['Destination']
|
@@ -120,6 +150,7 @@ module SAML2
|
|
120
150
|
@destination
|
121
151
|
end
|
122
152
|
|
153
|
+
# @return [NameID, nil]
|
123
154
|
def issuer
|
124
155
|
@issuer ||= NameID.from_xml(xml.at_xpath('saml:Issuer', Namespaces::ALL))
|
125
156
|
end
|
data/lib/saml2/name_id.rb
CHANGED
@@ -1,27 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'saml2/base'
|
1
4
|
require 'saml2/namespaces'
|
2
5
|
|
3
6
|
module SAML2
|
4
|
-
class NameID
|
7
|
+
class NameID < Base
|
5
8
|
module Format
|
6
|
-
EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
7
|
-
ENTITY = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
|
8
|
-
KERBEROS = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"
|
9
|
-
PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
10
|
-
TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
11
|
-
UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
12
|
-
WINDOWS_DOMAIN_QUALIFIED_NAME = "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName"
|
13
|
-
X509_SUBJECT_NAME = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"
|
9
|
+
EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
10
|
+
ENTITY = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
|
11
|
+
KERBEROS = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos" # name[/instance]@REALM
|
12
|
+
PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" # opaque, pseudo-random, unique per SP-IdP pair
|
13
|
+
TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" # opaque, will likely change
|
14
|
+
UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
15
|
+
WINDOWS_DOMAIN_QUALIFIED_NAME = "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName" # [DomainName\]UserName
|
16
|
+
X509_SUBJECT_NAME = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"
|
14
17
|
end
|
15
18
|
|
16
19
|
class Policy < Base
|
17
|
-
|
20
|
+
# @return [Boolean, nil]
|
21
|
+
attr_writer :allow_create
|
22
|
+
attr_writer :format, :sp_name_qualifier
|
18
23
|
|
24
|
+
# @param allow_create optional [Boolean]
|
25
|
+
# @param format optional [String]
|
26
|
+
# @param sp_name_qualifier optional [String]
|
19
27
|
def initialize(allow_create = nil, format = nil, sp_name_qualifier = nil)
|
20
28
|
@allow_create = allow_create if allow_create
|
21
29
|
@format = format if format
|
22
30
|
@sp_name_qualifier = sp_name_qualifier if sp_name_qualifier
|
23
31
|
end
|
24
32
|
|
33
|
+
# @return [Boolean, nil]
|
25
34
|
def allow_create?
|
26
35
|
if xml && !instance_variable_defined?(:@allow_create)
|
27
36
|
@allow_create = xml['AllowCreate']&.== 'true'
|
@@ -29,6 +38,8 @@ module SAML2
|
|
29
38
|
@allow_create
|
30
39
|
end
|
31
40
|
|
41
|
+
# @see Format
|
42
|
+
# @return [String, nil]
|
32
43
|
def format
|
33
44
|
if xml && !instance_variable_defined?(:@format)
|
34
45
|
@format = xml['Format']
|
@@ -36,6 +47,7 @@ module SAML2
|
|
36
47
|
@format
|
37
48
|
end
|
38
49
|
|
50
|
+
# @return [String, nil]
|
39
51
|
def sp_name_qualifier
|
40
52
|
if xml && !instance_variable_defined?(:@sp_name_qualifier)
|
41
53
|
@sp_name_qualifier = xml['SPNameQualifier']
|
@@ -43,12 +55,15 @@ module SAML2
|
|
43
55
|
@sp_name_qualifier
|
44
56
|
end
|
45
57
|
|
58
|
+
# @param rhs [Policy]
|
59
|
+
# @return [Boolean]
|
46
60
|
def ==(rhs)
|
47
61
|
allow_create? == rhs.allow_create? &&
|
48
62
|
format == rhs.format &&
|
49
63
|
sp_name_qualifier == rhs.sp_name_qualifier
|
50
64
|
end
|
51
65
|
|
66
|
+
# (see Base#build)
|
52
67
|
def build(builder)
|
53
68
|
builder['samlp'].NameIDPolicy do |name_id_policy|
|
54
69
|
name_id_policy.parent['Format'] = format if format
|
@@ -58,20 +73,30 @@ module SAML2
|
|
58
73
|
end
|
59
74
|
end
|
60
75
|
|
61
|
-
|
76
|
+
# @return [String]
|
77
|
+
attr_accessor :id
|
78
|
+
# @return [String, nil]
|
79
|
+
attr_accessor :format, :name_qualifier, :sp_name_qualifier
|
62
80
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
# (see Base#from_xml)
|
82
|
+
def from_xml(node)
|
83
|
+
self.id = node.content.strip
|
84
|
+
self.format = node['Format']
|
85
|
+
self.name_qualifier = node['NameQualifier']
|
86
|
+
self.sp_name_qualifier = node['SPNameQualifier']
|
68
87
|
end
|
69
88
|
|
89
|
+
# @param id [String]
|
90
|
+
# @param format optional [String]
|
91
|
+
# @param name_qualifier optional [String]
|
92
|
+
# @param sp_name_qualifier optional [String]
|
70
93
|
def initialize(id = nil, format = nil, name_qualifier: nil, sp_name_qualifier: nil)
|
71
94
|
@id, @format, @name_qualifier, @sp_name_qualifier =
|
72
95
|
id, format, name_qualifier, sp_name_qualifier
|
73
96
|
end
|
74
97
|
|
98
|
+
# @param rhs [NameID]
|
99
|
+
# @return [Boolean]
|
75
100
|
def ==(rhs)
|
76
101
|
id == rhs.id &&
|
77
102
|
format == rhs.format &&
|
@@ -79,6 +104,7 @@ module SAML2
|
|
79
104
|
sp_name_qualifier == rhs.sp_name_qualifier
|
80
105
|
end
|
81
106
|
|
107
|
+
# (see Base#build)
|
82
108
|
def build(builder, element: nil)
|
83
109
|
args = {}
|
84
110
|
args['Format'] = format if format
|
data/lib/saml2/namespaces.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SAML2
|
2
4
|
module Namespaces
|
3
|
-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
4
|
-
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
5
|
-
SAML = "urn:oasis:names:tc:SAML:2.0:assertion"
|
6
|
-
SAMLP = "urn:oasis:names:tc:SAML:2.0:protocol"
|
7
|
-
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
8
|
-
XS = "http://www.w3.org/2001/XMLSchema"
|
9
|
-
XSI = "http://www.w3.org/2001/XMLSchema-instance"
|
10
|
-
X500 = "urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
|
5
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
6
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
7
|
+
SAML = "urn:oasis:names:tc:SAML:2.0:assertion"
|
8
|
+
SAMLP = "urn:oasis:names:tc:SAML:2.0:protocol"
|
9
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
10
|
+
XS = "http://www.w3.org/2001/XMLSchema"
|
11
|
+
XSI = "http://www.w3.org/2001/XMLSchema-instance"
|
12
|
+
X500 = "urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
|
11
13
|
|
12
14
|
ALL = {
|
13
15
|
'xmlns:dsig' => DSIG,
|
data/lib/saml2/organization.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/base'
|
2
4
|
require 'saml2/localized_name'
|
3
5
|
require 'saml2/namespaces'
|
4
6
|
|
5
7
|
module SAML2
|
6
8
|
class Organization < Base
|
9
|
+
# @return [LocalizedName]
|
7
10
|
attr_reader :name, :display_name, :url
|
8
11
|
|
12
|
+
# (see Base#from_xml)
|
9
13
|
def from_xml(node)
|
10
14
|
name.from_xml(node.xpath('md:OrganizationName', Namespaces::ALL))
|
11
15
|
display_name.from_xml(node.xpath('md:OrganizationDisplayName', Namespaces::ALL))
|
@@ -18,6 +22,7 @@ module SAML2
|
|
18
22
|
@url = LocalizedName.new('OrganizationURL', url)
|
19
23
|
end
|
20
24
|
|
25
|
+
# (see Base#build)
|
21
26
|
def build(builder)
|
22
27
|
builder['md'].Organization do |organization|
|
23
28
|
@name.build(organization)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/contact'
|
2
4
|
require 'saml2/organization'
|
3
5
|
|
@@ -10,12 +12,14 @@ module SAML2
|
|
10
12
|
@contacts = []
|
11
13
|
end
|
12
14
|
|
15
|
+
# (see Base#from_xml)
|
13
16
|
def from_xml(node)
|
14
17
|
remove_instance_variable(:@organization)
|
15
18
|
@contacts = nil
|
16
19
|
super
|
17
20
|
end
|
18
21
|
|
22
|
+
# @return [Organization, nil]
|
19
23
|
def organization
|
20
24
|
unless instance_variable_defined?(:@organization)
|
21
25
|
@organization = Organization.from_xml(xml.at_xpath('md:Organization', Namespaces::ALL))
|
@@ -23,6 +27,7 @@ module SAML2
|
|
23
27
|
@organization
|
24
28
|
end
|
25
29
|
|
30
|
+
# @return [Array<Contact>]
|
26
31
|
def contacts
|
27
32
|
@contacts ||= load_object_array(xml, 'md:ContactPerson', Contact)
|
28
33
|
end
|
data/lib/saml2/request.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/base'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class RequestedAuthnContext < Base
|
5
|
-
|
7
|
+
# @return [String, nil]
|
8
|
+
attr_accessor :comparison
|
9
|
+
# @return [String, Array<String>]
|
10
|
+
attr_accessor :class_ref
|
6
11
|
|
12
|
+
# (see Base#build)
|
7
13
|
def build(builder)
|
8
14
|
builder['samlp'].RequestedAuthnContext do |requested_authn_context|
|
9
15
|
requested_authn_context.parent['Comparison'] = comparison.to_s if comparison
|
data/lib/saml2/response.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'nokogiri-xmlsec'
|
2
4
|
require 'time'
|
3
5
|
|
@@ -10,6 +12,12 @@ module SAML2
|
|
10
12
|
class Response < StatusResponse
|
11
13
|
attr_reader :assertions
|
12
14
|
|
15
|
+
# Respond to an {AuthnRequest}
|
16
|
+
# @param authn_request [AuthnRequest]
|
17
|
+
# @param issuer [NameID]
|
18
|
+
# @param name_id [NameID] The Subject
|
19
|
+
# @param attributes optional [Hash<String => String>, Array<Attribute>]
|
20
|
+
# @return [Response]
|
13
21
|
def self.respond_to(authn_request, issuer, name_id, attributes = nil)
|
14
22
|
response = initiate(nil, issuer, name_id)
|
15
23
|
response.in_response_to = authn_request.id
|
@@ -26,6 +34,12 @@ module SAML2
|
|
26
34
|
response
|
27
35
|
end
|
28
36
|
|
37
|
+
# Begin an IdP Initiated login
|
38
|
+
# @param service_provider [ServiceProvider]
|
39
|
+
# @param issuer [NameID]
|
40
|
+
# @param name_id [NameID] The subject
|
41
|
+
# @param attributes optional [Hash<String => String>, Array<Attribute>]
|
42
|
+
# @return [Response]
|
29
43
|
def self.initiate(service_provider, issuer, name_id, attributes = nil)
|
30
44
|
response = new
|
31
45
|
response.issuer = issuer
|
@@ -57,11 +71,13 @@ module SAML2
|
|
57
71
|
@assertions = []
|
58
72
|
end
|
59
73
|
|
74
|
+
# (see Base#from_xml)
|
60
75
|
def from_xml(node)
|
61
76
|
super
|
62
77
|
remove_instance_variable(:@assertions)
|
63
78
|
end
|
64
79
|
|
80
|
+
# @return [Array<Assertion>]
|
65
81
|
def assertions
|
66
82
|
unless instance_variable_defined?(:@assertions)
|
67
83
|
@assertions = load_object_array(xml, 'saml:Assertion', Assertion)
|
@@ -69,8 +85,10 @@ module SAML2
|
|
69
85
|
@assertions
|
70
86
|
end
|
71
87
|
|
72
|
-
|
73
|
-
|
88
|
+
# (see Signable#sign)
|
89
|
+
# Signs each assertion.
|
90
|
+
def sign(x509_certificate, private_key, algorithm_name = :sha256)
|
91
|
+
assertions.each { |assertion| assertion.sign(x509_certificate, private_key, algorithm_name) }
|
74
92
|
# make sure we no longer pretty print this object
|
75
93
|
@pretty = false
|
76
94
|
nil
|
data/lib/saml2/role.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
require 'saml2/base'
|
@@ -6,9 +8,10 @@ require 'saml2/organization_and_contacts'
|
|
6
8
|
require 'saml2/signable'
|
7
9
|
|
8
10
|
module SAML2
|
11
|
+
# @abstract
|
9
12
|
class Role < Base
|
10
13
|
module Protocols
|
11
|
-
SAML2 = 'urn:oasis:names:tc:SAML:2.0:protocol'
|
14
|
+
SAML2 = 'urn:oasis:names:tc:SAML:2.0:protocol'
|
12
15
|
end
|
13
16
|
|
14
17
|
include OrganizationAndContacts
|
@@ -23,29 +26,36 @@ module SAML2
|
|
23
26
|
@keys = []
|
24
27
|
end
|
25
28
|
|
29
|
+
# (see Base#from_xml)
|
26
30
|
def from_xml(node)
|
27
31
|
super
|
28
32
|
@supported_protocols = nil
|
29
33
|
@keys = nil
|
30
34
|
end
|
31
35
|
|
36
|
+
# @see Protocols
|
37
|
+
# @return [Array<String>]
|
32
38
|
def supported_protocols
|
33
39
|
@supported_protocols ||= xml['protocolSupportEnumeration'].split
|
34
40
|
end
|
35
41
|
|
42
|
+
# @return [Array<KeyDescriptor>]
|
36
43
|
def keys
|
37
|
-
@keys ||= load_object_array(xml, 'md:KeyDescriptor',
|
44
|
+
@keys ||= load_object_array(xml, 'md:KeyDescriptor', KeyDescriptor)
|
38
45
|
end
|
39
46
|
|
47
|
+
# @return [Array<KeyDescriptor>]
|
40
48
|
def signing_keys
|
41
49
|
keys.select { |key| key.signing? }
|
42
50
|
end
|
43
51
|
|
52
|
+
# @return [Array<KeyDescriptor>]
|
44
53
|
def encryption_keys
|
45
54
|
keys.select { |key| key.encryption? }
|
46
55
|
end
|
47
56
|
|
48
57
|
protected
|
58
|
+
|
49
59
|
# should be called from inside the role element
|
50
60
|
def build(builder)
|
51
61
|
builder.parent['protocolSupportEnumeration'] = supported_protocols.to_a.join(' ')
|