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,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(' ')
|