saml-kit 1.0.6 → 1.0.7
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/.gitlab-ci.yml +5 -5
- data/.rubocop.yml +92 -0
- data/.rubocop_todo.yml +45 -0
- data/.travis.yml +7 -3
- data/Gemfile +2 -2
- data/Rakefile +5 -3
- data/bin/cibuild +23 -0
- data/bin/console +3 -3
- data/bin/lint +13 -0
- data/bin/setup +1 -1
- data/bin/test +19 -0
- data/exe/saml-kit-create-self-signed-certificate +6 -6
- data/exe/saml-kit-decode-http-redirect +6 -2
- data/lib/saml/kit.rb +42 -39
- data/lib/saml/kit/assertion.rb +67 -25
- data/lib/saml/kit/authentication_request.rb +1 -1
- data/lib/saml/kit/bindings.rb +8 -8
- data/lib/saml/kit/bindings/binding.rb +5 -5
- data/lib/saml/kit/bindings/http_redirect.rb +12 -7
- data/lib/saml/kit/bindings/url_builder.rb +2 -2
- data/lib/saml/kit/buildable.rb +3 -3
- data/lib/saml/kit/builders/assertion.rb +4 -0
- data/lib/saml/kit/builders/authentication_request.rb +3 -3
- data/lib/saml/kit/builders/logout_request.rb +1 -1
- data/lib/saml/kit/builders/logout_response.rb +1 -1
- data/lib/saml/kit/builders/response.rb +2 -8
- data/lib/saml/kit/builders/templates/assertion.builder +1 -1
- data/lib/saml/kit/builders/templates/metadata.builder +4 -4
- data/lib/saml/kit/builders/templates/service_provider_metadata.builder +1 -1
- data/lib/saml/kit/composite_metadata.rb +9 -5
- data/lib/saml/kit/configuration.rb +7 -7
- data/lib/saml/kit/default_registry.rb +1 -1
- data/lib/saml/kit/document.rb +39 -23
- data/lib/saml/kit/identity_provider_metadata.rb +6 -6
- data/lib/saml/kit/invalid_document.rb +2 -2
- data/lib/saml/kit/locales/en.yml +12 -3
- data/lib/saml/kit/logout_request.rb +1 -1
- data/lib/saml/kit/logout_response.rb +1 -1
- data/lib/saml/kit/metadata.rb +43 -41
- data/lib/saml/kit/namespaces.rb +25 -25
- data/lib/saml/kit/null_assertion.rb +17 -0
- data/lib/saml/kit/respondable.rb +2 -3
- data/lib/saml/kit/response.rb +23 -4
- data/lib/saml/kit/rspec/have_query_param.rb +1 -1
- data/lib/saml/kit/service_provider_metadata.rb +3 -3
- data/lib/saml/kit/signature.rb +74 -4
- data/lib/saml/kit/translatable.rb +3 -2
- data/lib/saml/kit/trustable.rb +4 -11
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml_templatable.rb +10 -5
- data/saml-kit.gemspec +25 -22
- metadata +54 -6
@@ -32,15 +32,15 @@ module Saml
|
|
32
32
|
# {include:file:spec/examples/identity_provider_metadata_spec.rb}
|
33
33
|
class IdentityProviderMetadata < Metadata
|
34
34
|
def initialize(xml)
|
35
|
-
super(
|
35
|
+
super('IDPSSODescriptor', xml)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Returns the IDPSSODescriptor/@WantAuthnRequestsSigned attribute.
|
39
39
|
def want_authn_requests_signed
|
40
40
|
xpath = "/md:EntityDescriptor/md:#{name}"
|
41
|
-
attribute = document.find_by(xpath).attribute(
|
41
|
+
attribute = document.find_by(xpath).attribute('WantAuthnRequestsSigned')
|
42
42
|
return true if attribute.nil?
|
43
|
-
attribute.text.
|
43
|
+
attribute.text.casecmp('true').zero?
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns each of the SingleSignOnService elements.
|
@@ -59,8 +59,8 @@ module Saml
|
|
59
59
|
def attributes
|
60
60
|
document.find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
|
61
61
|
{
|
62
|
-
format: item.attribute(
|
63
|
-
name: item.attribute(
|
62
|
+
format: item.attribute('NameFormat').try(:value),
|
63
|
+
name: item.attribute('Name').value,
|
64
64
|
}
|
65
65
|
end
|
66
66
|
end
|
@@ -71,7 +71,7 @@ module Saml
|
|
71
71
|
# @param relay_state [Object] The RelayState to include the returned SAML params.
|
72
72
|
# @param configuration [Saml::Kit::Configuration] the configuration to use for generating the request.
|
73
73
|
# @return [Array] The url and saml params encoded using the rules for the specified binding.
|
74
|
-
def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
|
74
|
+
def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
|
75
75
|
builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |x|
|
76
76
|
x.embed_signature = want_authn_requests_signed
|
77
77
|
yield x if block_given?
|
data/lib/saml/kit/locales/en.yml
CHANGED
@@ -3,12 +3,13 @@ en:
|
|
3
3
|
saml/kit:
|
4
4
|
errors:
|
5
5
|
Assertion:
|
6
|
+
cannot_decrypt: "cannot be decrypted."
|
6
7
|
expired: "must not be expired."
|
7
8
|
must_match_issuer: "must match entityId."
|
8
9
|
must_contain_single_assertion: "must contain single Assertion."
|
9
10
|
AuthnRequest:
|
10
11
|
invalid: "must contain AuthnRequest."
|
11
|
-
invalid_fingerprint: "
|
12
|
+
invalid_fingerprint: "is not registered."
|
12
13
|
unregistered: "is unregistered."
|
13
14
|
IDPSSODescriptor:
|
14
15
|
invalid: "must contain IDPSSODescriptor."
|
@@ -16,16 +17,24 @@ en:
|
|
16
17
|
InvalidDocument:
|
17
18
|
invalid: "must contain valid SAMLRequest"
|
18
19
|
LogoutRequest:
|
19
|
-
invalid_fingerprint: "
|
20
|
+
invalid_fingerprint: "is not registered."
|
20
21
|
unregistered: "is unregistered."
|
21
22
|
LogoutResponse:
|
22
23
|
unregistered: "is unregistered."
|
24
|
+
NullAssertion:
|
25
|
+
invalid: "is missing."
|
23
26
|
Response:
|
24
27
|
invalid: "must contain Response."
|
25
|
-
invalid_fingerprint: "
|
28
|
+
invalid_fingerprint: "is not registered."
|
26
29
|
invalid_response_to: "must match request id."
|
27
30
|
invalid_version: "must be 2.0."
|
28
31
|
unregistered: "must originate from registered identity provider."
|
32
|
+
must_contain_single_assertion: "must contain a single Assertion."
|
33
|
+
Signature:
|
34
|
+
certificate: "Not valid before %{not_before}. Not valid after %{not_after}."
|
35
|
+
digest_value: "is invalid."
|
36
|
+
empty: "is missing."
|
37
|
+
signature: "is invalid."
|
29
38
|
SPSSODescriptor:
|
30
39
|
invalid: "must contain SPSSODescriptor."
|
31
40
|
invalid_signature: "invalid signature."
|
@@ -31,7 +31,7 @@ module Saml
|
|
31
31
|
# @param xml [String] The raw xml string.
|
32
32
|
# @param configuration [Saml::Kit::Configuration] the configuration to use.
|
33
33
|
def initialize(xml, configuration: Saml::Kit.configuration)
|
34
|
-
super(xml, name:
|
34
|
+
super(xml, name: 'LogoutRequest', configuration: configuration)
|
35
35
|
end
|
36
36
|
|
37
37
|
# Returns the NameID value.
|
@@ -10,7 +10,7 @@ module Saml
|
|
10
10
|
|
11
11
|
def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
|
12
12
|
@request_id = request_id
|
13
|
-
super(xml, name:
|
13
|
+
super(xml, name: 'LogoutResponse', configuration: configuration)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/saml/kit/metadata.rb
CHANGED
@@ -23,7 +23,11 @@ module Saml
|
|
23
23
|
# for a list of options that can be specified.
|
24
24
|
# {include:file:spec/examples/metadata_spec.rb}
|
25
25
|
class Metadata
|
26
|
-
|
26
|
+
include ActiveModel::Validations
|
27
|
+
include XsdValidatable
|
28
|
+
include Translatable
|
29
|
+
include Buildable
|
30
|
+
METADATA_XSD = File.expand_path('./xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)).freeze
|
27
31
|
NAMESPACES = {
|
28
32
|
"NameFormat": Namespaces::ATTR_SPLAT,
|
29
33
|
"ds": ::Xml::Kit::Namespaces::XMLDSIG,
|
@@ -31,10 +35,6 @@ module Saml
|
|
31
35
|
"saml": Namespaces::ASSERTION,
|
32
36
|
"samlp": Namespaces::PROTOCOL,
|
33
37
|
}.freeze
|
34
|
-
include ActiveModel::Validations
|
35
|
-
include XsdValidatable
|
36
|
-
include Translatable
|
37
|
-
include Buildable
|
38
38
|
|
39
39
|
validates_presence_of :metadata
|
40
40
|
validate :must_contain_descriptor
|
@@ -50,7 +50,7 @@ module Saml
|
|
50
50
|
|
51
51
|
# Returns the /EntityDescriptor/@entityID
|
52
52
|
def entity_id
|
53
|
-
document.find_by(
|
53
|
+
document.find_by('/md:EntityDescriptor/@entityID').value
|
54
54
|
end
|
55
55
|
|
56
56
|
# Returns the supported NameIDFormats.
|
@@ -60,23 +60,23 @@ module Saml
|
|
60
60
|
|
61
61
|
# Returns the Organization Name
|
62
62
|
def organization_name
|
63
|
-
document.find_by(
|
63
|
+
document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Returns the Organization URL
|
67
67
|
def organization_url
|
68
|
-
document.find_by(
|
68
|
+
document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text)
|
69
69
|
end
|
70
70
|
|
71
71
|
# Returns the Company
|
72
72
|
def contact_person_company
|
73
|
-
document.find_by(
|
73
|
+
document.find_by('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text)
|
74
74
|
end
|
75
75
|
|
76
76
|
# Returns each of the X509 certificates.
|
77
77
|
def certificates
|
78
78
|
@certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
|
79
|
-
cert = item.at_xpath(
|
79
|
+
cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', NAMESPACES).text
|
80
80
|
attribute = item.attribute('use')
|
81
81
|
use = attribute.nil? ? nil : item.attribute('use').value
|
82
82
|
::Xml::Kit::Certificate.new(cert, use: use)
|
@@ -98,8 +98,8 @@ module Saml
|
|
98
98
|
# @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
|
99
99
|
def services(type)
|
100
100
|
document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
|
101
|
-
binding = item.attribute(
|
102
|
-
location = item.attribute(
|
101
|
+
binding = item.attribute('Binding').value
|
102
|
+
location = item.attribute('Location').value
|
103
103
|
Saml::Kit::Bindings.create_for(binding, location)
|
104
104
|
end
|
105
105
|
end
|
@@ -179,25 +179,31 @@ module Saml
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
182
|
+
def signature
|
183
|
+
@signature ||= Signature.new(at_xpath('/md:EntityDescriptor/ds:Signature'))
|
184
|
+
end
|
185
|
+
|
186
|
+
class << self
|
187
|
+
# Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
|
188
|
+
#
|
189
|
+
# @param content [String] the raw metadata XML.
|
190
|
+
# @return [Saml::Kit::Metadata] the metadata document or subclass.
|
191
|
+
def from(content)
|
192
|
+
hash = Hash.from_xml(content)
|
193
|
+
entity_descriptor = hash['EntityDescriptor']
|
194
|
+
if entity_descriptor.key?('SPSSODescriptor') && entity_descriptor.key?('IDPSSODescriptor')
|
195
|
+
Saml::Kit::CompositeMetadata.new(content)
|
196
|
+
elsif entity_descriptor.keys.include?('SPSSODescriptor')
|
197
|
+
Saml::Kit::ServiceProviderMetadata.new(content)
|
198
|
+
elsif entity_descriptor.keys.include?('IDPSSODescriptor')
|
199
|
+
Saml::Kit::IdentityProviderMetadata.new(content)
|
200
|
+
end
|
195
201
|
end
|
196
|
-
end
|
197
202
|
|
198
|
-
|
199
|
-
|
200
|
-
|
203
|
+
# @!visibility private
|
204
|
+
def builder_class
|
205
|
+
Saml::Kit::Builders::Metadata
|
206
|
+
end
|
201
207
|
end
|
202
208
|
|
203
209
|
private
|
@@ -208,6 +214,10 @@ module Saml
|
|
208
214
|
@document ||= ::Xml::Kit::Document.new(xml, namespaces: NAMESPACES)
|
209
215
|
end
|
210
216
|
|
217
|
+
def at_xpath(xpath)
|
218
|
+
document.find_by(xpath)
|
219
|
+
end
|
220
|
+
|
211
221
|
def metadata
|
212
222
|
document.find_by("/md:EntityDescriptor/md:#{name}").present?
|
213
223
|
end
|
@@ -221,20 +231,12 @@ module Saml
|
|
221
231
|
end
|
222
232
|
|
223
233
|
def must_have_valid_signature
|
224
|
-
return
|
225
|
-
|
226
|
-
unless valid_signature?
|
227
|
-
errors[:base] << error_message(:invalid_signature)
|
228
|
-
end
|
229
|
-
end
|
234
|
+
return unless signature.present?
|
230
235
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
xml.errors.each do |error|
|
235
|
-
errors[:base] << error
|
236
|
+
signature.valid?
|
237
|
+
signature.errors.each do |attribute, error|
|
238
|
+
errors[attribute] << error
|
236
239
|
end
|
237
|
-
result
|
238
240
|
end
|
239
241
|
end
|
240
242
|
end
|
data/lib/saml/kit/namespaces.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
module Namespaces
|
4
|
-
SAML_2_0 =
|
5
|
-
SAML_1_1 =
|
6
|
-
ATTR_NAME_FORMAT = "#{SAML_2_0}:attrname-format"
|
7
|
-
NAME_ID_FORMAT_1_1 = "#{SAML_1_1}:nameid-format"
|
8
|
-
NAME_ID_FORMAT_2_0 = "#{SAML_2_0}:nameid-format"
|
9
|
-
STATUS = "#{SAML_2_0}:status"
|
4
|
+
SAML_2_0 = 'urn:oasis:names:tc:SAML:2.0'.freeze
|
5
|
+
SAML_1_1 = 'urn:oasis:names:tc:SAML:1.1'.freeze
|
6
|
+
ATTR_NAME_FORMAT = "#{SAML_2_0}:attrname-format".freeze
|
7
|
+
NAME_ID_FORMAT_1_1 = "#{SAML_1_1}:nameid-format".freeze
|
8
|
+
NAME_ID_FORMAT_2_0 = "#{SAML_2_0}:nameid-format".freeze
|
9
|
+
STATUS = "#{SAML_2_0}:status".freeze
|
10
10
|
|
11
|
-
ASSERTION = "#{SAML_2_0}:assertion"
|
12
|
-
ATTR_SPLAT = "#{ATTR_NAME_FORMAT}:*"
|
13
|
-
BASIC = "#{ATTR_NAME_FORMAT}:basic"
|
14
|
-
BEARER = "#{SAML_2_0}:cm:bearer"
|
15
|
-
EMAIL_ADDRESS = "#{NAME_ID_FORMAT_1_1}:emailAddress"
|
16
|
-
INVALID_NAME_ID_POLICY = "#{STATUS}:InvalidNameIDPolicy"
|
17
|
-
METADATA = "#{SAML_2_0}:metadata"
|
18
|
-
PASSWORD = "#{SAML_2_0}:ac:classes:Password"
|
19
|
-
PASSWORD_PROTECTED = "#{SAML_2_0}:ac:classes:PasswordProtectedTransport"
|
20
|
-
PERSISTENT = "#{NAME_ID_FORMAT_2_0}:persistent"
|
21
|
-
PROTOCOL = "#{SAML_2_0}:protocol"
|
22
|
-
REQUESTER_ERROR = "#{STATUS}:Requester"
|
23
|
-
RESPONDER_ERROR = "#{STATUS}:Responder"
|
24
|
-
SUCCESS = "#{STATUS}:Success"
|
25
|
-
TRANSIENT = "#{NAME_ID_FORMAT_2_0}:transient"
|
26
|
-
UNSPECIFIED = "#{SAML_2_0}:consent:unspecified"
|
27
|
-
UNSPECIFIED_NAMEID = "#{NAME_ID_FORMAT_1_1}:unspecified"
|
28
|
-
URI = "#{ATTR_NAME_FORMAT}:uri"
|
29
|
-
VERSION_MISMATCH_ERROR = "#{STATUS}:VersionMismatch"
|
11
|
+
ASSERTION = "#{SAML_2_0}:assertion".freeze
|
12
|
+
ATTR_SPLAT = "#{ATTR_NAME_FORMAT}:*".freeze
|
13
|
+
BASIC = "#{ATTR_NAME_FORMAT}:basic".freeze
|
14
|
+
BEARER = "#{SAML_2_0}:cm:bearer".freeze
|
15
|
+
EMAIL_ADDRESS = "#{NAME_ID_FORMAT_1_1}:emailAddress".freeze
|
16
|
+
INVALID_NAME_ID_POLICY = "#{STATUS}:InvalidNameIDPolicy".freeze
|
17
|
+
METADATA = "#{SAML_2_0}:metadata".freeze
|
18
|
+
PASSWORD = "#{SAML_2_0}:ac:classes:Password".freeze
|
19
|
+
PASSWORD_PROTECTED = "#{SAML_2_0}:ac:classes:PasswordProtectedTransport".freeze
|
20
|
+
PERSISTENT = "#{NAME_ID_FORMAT_2_0}:persistent".freeze
|
21
|
+
PROTOCOL = "#{SAML_2_0}:protocol".freeze
|
22
|
+
REQUESTER_ERROR = "#{STATUS}:Requester".freeze
|
23
|
+
RESPONDER_ERROR = "#{STATUS}:Responder".freeze
|
24
|
+
SUCCESS = "#{STATUS}:Success".freeze
|
25
|
+
TRANSIENT = "#{NAME_ID_FORMAT_2_0}:transient".freeze
|
26
|
+
UNSPECIFIED = "#{SAML_2_0}:consent:unspecified".freeze
|
27
|
+
UNSPECIFIED_NAMEID = "#{NAME_ID_FORMAT_1_1}:unspecified".freeze
|
28
|
+
URI = "#{ATTR_NAME_FORMAT}:uri".freeze
|
29
|
+
VERSION_MISMATCH_ERROR = "#{STATUS}:VersionMismatch".freeze
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class NullAssertion
|
4
|
+
include ActiveModel::Validations
|
5
|
+
include Translatable
|
6
|
+
validate :invalid
|
7
|
+
|
8
|
+
def invalid
|
9
|
+
errors[:assertion].push(error_message(:invalid))
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
'NullAssertion'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/saml/kit/respondable.rb
CHANGED
@@ -33,10 +33,9 @@ module Saml
|
|
33
33
|
|
34
34
|
def must_match_request_id
|
35
35
|
return if request_id.nil?
|
36
|
+
return if in_response_to == request_id
|
36
37
|
|
37
|
-
|
38
|
-
errors[:in_response_to] << error_message(:invalid_response_to)
|
39
|
-
end
|
38
|
+
errors[:in_response_to] << error_message(:invalid_response_to)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
data/lib/saml/kit/response.rb
CHANGED
@@ -8,14 +8,23 @@ module Saml
|
|
8
8
|
def_delegators :assertion, :name_id, :[], :attributes
|
9
9
|
|
10
10
|
validate :must_be_valid_assertion
|
11
|
+
validate :must_contain_single_assertion
|
11
12
|
|
12
13
|
def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
|
13
14
|
@request_id = request_id
|
14
|
-
super(xml, name:
|
15
|
+
super(xml, name: 'Response', configuration: configuration)
|
15
16
|
end
|
16
17
|
|
17
|
-
def assertion
|
18
|
-
@assertion ||=
|
18
|
+
def assertion(private_keys = configuration.private_keys(use: :encryption))
|
19
|
+
@assertion ||=
|
20
|
+
begin
|
21
|
+
node = assertion_nodes.last
|
22
|
+
if node.nil?
|
23
|
+
Saml::Kit::NullAssertion.new
|
24
|
+
else
|
25
|
+
Saml::Kit::Assertion.new(node, configuration: @configuration, private_keys: private_keys)
|
26
|
+
end
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
private
|
@@ -23,9 +32,19 @@ module Saml
|
|
23
32
|
def must_be_valid_assertion
|
24
33
|
assertion.valid?
|
25
34
|
assertion.errors.each do |attribute, error|
|
26
|
-
|
35
|
+
attribute = :assertion if attribute == :base
|
36
|
+
errors[attribute] << error
|
27
37
|
end
|
28
38
|
end
|
39
|
+
|
40
|
+
def must_contain_single_assertion
|
41
|
+
return if assertion_nodes.count <= 1
|
42
|
+
errors[:base] << error_message(:must_contain_single_assertion)
|
43
|
+
end
|
44
|
+
|
45
|
+
def assertion_nodes
|
46
|
+
search(Saml::Kit::Assertion::XPATH)
|
47
|
+
end
|
29
48
|
end
|
30
49
|
end
|
31
50
|
end
|
@@ -3,7 +3,7 @@ module Saml
|
|
3
3
|
# {include:file:spec/examples/service_provider_metadata_spec.rb}
|
4
4
|
class ServiceProviderMetadata < Metadata
|
5
5
|
def initialize(xml)
|
6
|
-
super(
|
6
|
+
super('SPSSODescriptor', xml)
|
7
7
|
end
|
8
8
|
|
9
9
|
# Returns each of the AssertionConsumerService bindings.
|
@@ -20,9 +20,9 @@ module Saml
|
|
20
20
|
|
21
21
|
# Returns true when the metadata demands that Assertions must be signed.
|
22
22
|
def want_assertions_signed
|
23
|
-
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute(
|
23
|
+
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
|
24
24
|
return true if attribute.nil?
|
25
|
-
attribute.text.
|
25
|
+
attribute.text.casecmp('true').zero?
|
26
26
|
end
|
27
27
|
|
28
28
|
# @!visibility private
|
data/lib/saml/kit/signature.rb
CHANGED
@@ -2,14 +2,21 @@ module Saml
|
|
2
2
|
module Kit
|
3
3
|
class Signature
|
4
4
|
include ActiveModel::Validations
|
5
|
+
include Translatable
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
validate :validate_signature
|
8
|
+
validate :validate_certificate
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(node)
|
13
|
+
@name = 'Signature'
|
14
|
+
@node = node
|
8
15
|
end
|
9
16
|
|
10
17
|
# Returns the embedded X509 Certificate
|
11
18
|
def certificate
|
12
|
-
value =
|
19
|
+
value = at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate').try(:text)
|
13
20
|
return if value.nil?
|
14
21
|
::Xml::Kit::Certificate.new(value, use: :signing)
|
15
22
|
end
|
@@ -20,9 +27,72 @@ module Saml
|
|
20
27
|
metadata.matches?(certificate.fingerprint, use: :signing)
|
21
28
|
end
|
22
29
|
|
30
|
+
def digest_value
|
31
|
+
at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestValue').try(:text)
|
32
|
+
end
|
33
|
+
|
34
|
+
def digest_method
|
35
|
+
at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm').try(:value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def signature_value
|
39
|
+
at_xpath('./ds:SignatureValue').try(:text)
|
40
|
+
end
|
41
|
+
|
42
|
+
def signature_method
|
43
|
+
at_xpath('./ds:SignedInfo/ds:SignatureMethod/@Algorithm').try(:value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def canonicalization_method
|
47
|
+
at_xpath('./ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm').try(:value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def transforms
|
51
|
+
node.search('./ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform/@Algorithm', Saml::Kit::Document::NAMESPACES).try(:map, &:value)
|
52
|
+
end
|
53
|
+
|
23
54
|
# Returns the XML Hash.
|
24
55
|
def to_h
|
25
|
-
@xml_hash
|
56
|
+
@xml_hash ||= present? ? Hash.from_xml(to_xml)['Signature'] : {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def present?
|
60
|
+
node
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_xml
|
64
|
+
node.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :node
|
70
|
+
|
71
|
+
def validate_signature
|
72
|
+
return errors[:base].push(error_message(:empty)) if certificate.nil?
|
73
|
+
|
74
|
+
signature = Xmldsig::Signature.new(@node, 'ID=$uri or @Id')
|
75
|
+
return if signature.valid?(certificate.x509)
|
76
|
+
signature.errors.each do |attribute|
|
77
|
+
errors.add(attribute, error_message(attribute))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_certificate(now = Time.now.utc)
|
82
|
+
return unless certificate.present?
|
83
|
+
return if certificate.active?(now)
|
84
|
+
|
85
|
+
message = error_message(
|
86
|
+
:certificate,
|
87
|
+
not_before: certificate.not_before,
|
88
|
+
not_after: certificate.not_after
|
89
|
+
)
|
90
|
+
errors.add(:certificate, message)
|
91
|
+
end
|
92
|
+
|
93
|
+
def at_xpath(xpath)
|
94
|
+
return nil unless node
|
95
|
+
node.at_xpath(xpath, Saml::Kit::Document::NAMESPACES)
|
26
96
|
end
|
27
97
|
end
|
28
98
|
end
|