saml-kit 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +5 -5
  3. data/.rubocop.yml +92 -0
  4. data/.rubocop_todo.yml +45 -0
  5. data/.travis.yml +7 -3
  6. data/Gemfile +2 -2
  7. data/Rakefile +5 -3
  8. data/bin/cibuild +23 -0
  9. data/bin/console +3 -3
  10. data/bin/lint +13 -0
  11. data/bin/setup +1 -1
  12. data/bin/test +19 -0
  13. data/exe/saml-kit-create-self-signed-certificate +6 -6
  14. data/exe/saml-kit-decode-http-redirect +6 -2
  15. data/lib/saml/kit.rb +42 -39
  16. data/lib/saml/kit/assertion.rb +67 -25
  17. data/lib/saml/kit/authentication_request.rb +1 -1
  18. data/lib/saml/kit/bindings.rb +8 -8
  19. data/lib/saml/kit/bindings/binding.rb +5 -5
  20. data/lib/saml/kit/bindings/http_redirect.rb +12 -7
  21. data/lib/saml/kit/bindings/url_builder.rb +2 -2
  22. data/lib/saml/kit/buildable.rb +3 -3
  23. data/lib/saml/kit/builders/assertion.rb +4 -0
  24. data/lib/saml/kit/builders/authentication_request.rb +3 -3
  25. data/lib/saml/kit/builders/logout_request.rb +1 -1
  26. data/lib/saml/kit/builders/logout_response.rb +1 -1
  27. data/lib/saml/kit/builders/response.rb +2 -8
  28. data/lib/saml/kit/builders/templates/assertion.builder +1 -1
  29. data/lib/saml/kit/builders/templates/metadata.builder +4 -4
  30. data/lib/saml/kit/builders/templates/service_provider_metadata.builder +1 -1
  31. data/lib/saml/kit/composite_metadata.rb +9 -5
  32. data/lib/saml/kit/configuration.rb +7 -7
  33. data/lib/saml/kit/default_registry.rb +1 -1
  34. data/lib/saml/kit/document.rb +39 -23
  35. data/lib/saml/kit/identity_provider_metadata.rb +6 -6
  36. data/lib/saml/kit/invalid_document.rb +2 -2
  37. data/lib/saml/kit/locales/en.yml +12 -3
  38. data/lib/saml/kit/logout_request.rb +1 -1
  39. data/lib/saml/kit/logout_response.rb +1 -1
  40. data/lib/saml/kit/metadata.rb +43 -41
  41. data/lib/saml/kit/namespaces.rb +25 -25
  42. data/lib/saml/kit/null_assertion.rb +17 -0
  43. data/lib/saml/kit/respondable.rb +2 -3
  44. data/lib/saml/kit/response.rb +23 -4
  45. data/lib/saml/kit/rspec/have_query_param.rb +1 -1
  46. data/lib/saml/kit/service_provider_metadata.rb +3 -3
  47. data/lib/saml/kit/signature.rb +74 -4
  48. data/lib/saml/kit/translatable.rb +3 -2
  49. data/lib/saml/kit/trustable.rb +4 -11
  50. data/lib/saml/kit/version.rb +1 -1
  51. data/lib/saml/kit/xml_templatable.rb +10 -5
  52. data/saml-kit.gemspec +25 -22
  53. 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("IDPSSODescriptor", xml)
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("WantAuthnRequestsSigned")
41
+ attribute = document.find_by(xpath).attribute('WantAuthnRequestsSigned')
42
42
  return true if attribute.nil?
43
- attribute.text.downcase == "true"
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("NameFormat").try(:value),
63
- name: item.attribute("Name").value,
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) # :yields builder
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?
@@ -7,12 +7,12 @@ module Saml
7
7
  end
8
8
 
9
9
  def initialize(xml, configuration: nil)
10
- super(xml, name: "InvalidDocument")
10
+ super(xml, name: 'InvalidDocument')
11
11
  end
12
12
 
13
13
  def to_h
14
14
  super
15
- rescue
15
+ rescue StandardError
16
16
  {}
17
17
  end
18
18
  end
@@ -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: "does not match."
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: "does not match."
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: "does not match."
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: "LogoutRequest", configuration: configuration)
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: "LogoutResponse", configuration: configuration)
13
+ super(xml, name: 'LogoutResponse', configuration: configuration)
14
14
  end
15
15
  end
16
16
  end
@@ -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
- METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
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("/md:EntityDescriptor/@entityID").value
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("/md:EntityDescriptor/md:Organization/md:OrganizationName").try(:text)
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("/md:EntityDescriptor/md:Organization/md:OrganizationURL").try(:text)
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("/md:EntityDescriptor/md:ContactPerson/md:Company").try(:text)
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("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", NAMESPACES).text
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("Binding").value
102
- location = item.attribute("Location").value
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
- # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
183
- #
184
- # @param content [String] the raw metadata XML.
185
- # @return [Saml::Kit::Metadata] the metadata document or subclass.
186
- def self.from(content)
187
- hash = Hash.from_xml(content)
188
- entity_descriptor = hash["EntityDescriptor"]
189
- if entity_descriptor.key?("SPSSODescriptor") && entity_descriptor.key?("IDPSSODescriptor")
190
- Saml::Kit::CompositeMetadata.new(content)
191
- elsif entity_descriptor.keys.include?("SPSSODescriptor")
192
- Saml::Kit::ServiceProviderMetadata.new(content)
193
- elsif entity_descriptor.keys.include?("IDPSSODescriptor")
194
- Saml::Kit::IdentityProviderMetadata.new(content)
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
- # @!visibility private
199
- def self.builder_class
200
- Saml::Kit::Builders::Metadata
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 if to_xml.blank?
225
-
226
- unless valid_signature?
227
- errors[:base] << error_message(:invalid_signature)
228
- end
229
- end
234
+ return unless signature.present?
230
235
 
231
- def valid_signature?
232
- xml = document
233
- result = xml.valid?
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
@@ -1,32 +1,32 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Namespaces
4
- SAML_2_0 = "urn:oasis:names:tc:SAML:2.0"
5
- SAML_1_1 = "urn:oasis:names:tc: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
@@ -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
- if in_response_to != request_id
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
@@ -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: "Response", configuration: configuration)
15
+ super(xml, name: 'Response', configuration: configuration)
15
16
  end
16
17
 
17
- def assertion
18
- @assertion ||= Saml::Kit::Assertion.new(to_h, configuration: @configuration)
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
- self.errors[attribute] << error
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
@@ -6,7 +6,7 @@ RSpec::Matchers.define :have_query_param do |key|
6
6
  end
7
7
 
8
8
  def query_params_from(url)
9
- Hash[query_for(url).split("&").map { |x| x.split('=', 2) }]
9
+ Hash[query_for(url).split('&').map { |x| x.split('=', 2) }]
10
10
  end
11
11
 
12
12
  def uri_for(url)
@@ -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("SPSSODescriptor", xml)
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("WantAssertionsSigned")
23
+ attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
24
24
  return true if attribute.nil?
25
- attribute.text.downcase == "true"
25
+ attribute.text.casecmp('true').zero?
26
26
  end
27
27
 
28
28
  # @!visibility private
@@ -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
- def initialize(xml_hash)
7
- @xml_hash = xml_hash
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 = to_h.fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
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