class Sepa::Response

Handles soap responses got back from the bank. Bank specific functionality is defined in subclasses. Handles i.e. logic to make sure the response's integrity has not been compromised and has methods to extract content from the response.

Attributes

command[R]

The command with which the response was initialized

@return [Symbol]

environment[R]

The environment in which the request was sent

@return [Symbol]

error[R]

Possible Savon::Error with which the {Response} was initialized

@return [String]

soap[R]

The raw soap response in xml

@return [String]

Public Class Methods

new(hash = {}) click to toggle source

Initializes the response with a options hash

@param hash [Hash] Hash of options @example Possible keys in options hash

{
  response: "something",
  command: :get_user_info,
  error: "I'm error",
  encryption_private_key: OpenSSL::PKey::RSA
}
# File lib/sepa/response.rb, line 47
def initialize(hash = {})
  @command                = hash[:command]
  @encryption_private_key = hash[:encryption_private_key]
  @environment            = hash[:environment]
  @error                  = hash[:error]
  @soap                   = hash[:response]
end

Public Instance Methods

application_response(namespace: BXD) click to toggle source

Gets the application response from the response as an xml document. Makes a call to {#extract_application_response} to do the extraction.

@return [String] The application response as a raw xml document

# File lib/sepa/response.rb, line 124
def application_response(namespace: BXD)
  @application_response ||= extract_application_response(namespace)
end
bank_encryption_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 189
def bank_encryption_certificate; end
bank_root_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 195
def bank_root_certificate; end
bank_signing_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 192
def bank_signing_certificate; end
ca_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 204
def ca_certificate; end
certificate() click to toggle source

Returns the certificate embedded in the response

@return [OpenSSL::X509::Certificate] if the certificate is found @return [nil] if the certificate can't be found @raise [OpenSSL::X509::CertificateError] if the certificate cannot be processed

# File lib/sepa/response.rb, line 146
def certificate
  @certificate ||= begin
    extract_cert(doc, 'BinarySecurityToken', OASIS_SECEXT)
  end
end
content() click to toggle source

Returns the content of the response according to {#command}. When command is `:download_file`, content is returned as a base64 encoded string, when {#command} is `:download_file_list`, the content is returned as xml, when {#command} is `:get_user_info`, the content is returned as xml and when {#command} is `:upload_file`, content is returned as xml

@return [String] the content as xml or base64 encoded string

# File lib/sepa/response.rb, line 158
def content
  @content ||= begin
    xml = xml_doc(application_response)

    case @command
    when :download_file
      content_node = xml.at('xmlns|Content', xmlns: XML_DATA)
      content_node.content if content_node
    when :download_file_list
      content_node = xml.remove_namespaces!.at('FileDescriptors')
      content_node.to_xml if content_node
    when :get_user_info
      canonicalized_node(xml, XML_DATA, 'UserFileTypes')
    when :upload_file
      signature_node = xml.at('xmlns|Signature', xmlns: DSIG)
      if signature_node
        signature_node.remove
        xml.canonicalize
      end
    end
  end
end
doc() click to toggle source

Returns the soap of the response as a Nokogiri document

@return [Nokogiri::XML] The soap as Nokogiri document

# File lib/sepa/response.rb, line 58
def doc
  @doc = @soap ? xml_doc(@soap) : xml_doc(@error)
end
error_doc() click to toggle source

Returns the error of the response as a Nokogiri document

@return [Nokogiri::XML] The error as Nokogiri document

# File lib/sepa/response.rb, line 65
def error_doc
  @error_doc ||= xml_doc @error
end
file_references() click to toggle source

Returns the file references in a download file list response

@return [Array] File references

# File lib/sepa/response.rb, line 131
def file_references
  return unless @command == :download_file_list

  @file_references ||= begin
    xml = xml_doc content
    descriptors = xml.css('FileDescriptor')
    descriptors.map { |descriptor| descriptor.at('FileReference').content }
  end
end
hashes_match?(options = {}) click to toggle source

Verifies that all digest values in the response match the actual ones. Takes an optional verbose parameter to show which digests didn't match. The digest embedded in the document are first retrieved with {#find_digest_values} method and if none are found, false is returned. After this, nodes to calculate hashes from are retrieved and hashes using {#find_nodes_to_verify} method and after this the calculated digests are compared with the embedded ones. If the all match, true is returned. If some digests failed to verify and verbose parameter was passed, digests that failed to verify are printed to screen and false is returned. Otherwise just false is returned.

@param options [Hash] @return [false] if hashes don't match or aren't found @return [true] if hashes match @example Options hash

{ verbose: true }
# File lib/sepa/response.rb, line 83
def hashes_match?(options = {})
  digests = find_digest_values

  return false if digests.empty?

  nodes = find_nodes_to_verify(digests)

  verified_digests = digests.select do |uri, digest|
    uri = uri.sub(/^#/, '')
    digest == nodes[uri]
  end

  return true if digests == verified_digests

  unverified_digests = digests.select do |uri, digest|
    uri = uri.sub(/^#/, '')
    digest != nodes[uri]
  end

  if options[:verbose]
    puts "These digests failed to verify: #{unverified_digests}"
  end

  false
end
own_encryption_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 198
def own_encryption_certificate; end
own_signing_certificate() click to toggle source

@abstract

# File lib/sepa/response.rb, line 201
def own_signing_certificate; end
response_code(namespace: BXD, node_name: 'ResponseCode') click to toggle source

Returns the response code of the response

@return [String] if the response code can be found @return [nil] if the response code cannot be found

# File lib/sepa/response.rb, line 210
def response_code(namespace: BXD, node_name: 'ResponseCode')
  (node = doc.at("xmlns|#{node_name}", xmlns: namespace)) && node.content && node.content.rjust(2, '0')
end
response_text(namespace: BXD, node_name: 'ResponseText') click to toggle source

Returns the response text of the response

@return [String] if the response text can be found @return [nil] if the response text cannot be found

# File lib/sepa/response.rb, line 218
def response_text(namespace: BXD, node_name: 'ResponseText')
  (node = doc.at("xmlns|#{node_name}", xmlns: namespace)) && node.content
end
signature_is_valid?() click to toggle source

Verifies the signature by extracting the public key from the certificate embedded in the response and verifying the signature value with that. Makes a call to {#validate_signature} to do the actual verification. Passes `:exclusive` to {#validate_signature} so that exclusive mode of xml canonicalization is used.

@return [true] if signature is valid @return [false] if signature fails to verify

# File lib/sepa/response.rb, line 116
def signature_is_valid?
  validate_signature(doc, certificate, :exclusive)
end
to_s() click to toggle source

Returns the raw soap as xml

@return [String]

# File lib/sepa/response.rb, line 184
def to_s
  @soap
end

Private Instance Methods

client_errors() click to toggle source

Handles errors that have been passed from client

# File lib/sepa/response.rb, line 281
def client_errors
  client_error = error.to_s
  errors.add(:base, client_error) unless client_error.empty?
end
document_must_validate_against_schema() click to toggle source

Validates the document against soap schema unless {#error} is present or command is `:get_bank_certificate`

# File lib/sepa/response.rb, line 262
def document_must_validate_against_schema
  return if @error || command.to_sym == :get_bank_certificate

  check_validity_against_schema(doc, 'soap.xsd')
end
extract_application_response(namespace) click to toggle source

Extracts and returns application response from the response

@return [String] application response as raw xml if it can be found @return [nil] if application response cannot be found

# File lib/sepa/response.rb, line 272
def extract_application_response(namespace)
  ar_node = doc.at('xmlns|ApplicationResponse', xmlns: namespace)

  return unless ar_node

  decode(ar_node.content)
end
find_digest_values() click to toggle source

Finds all reference nodes with digest values in the document and returns a hash with uri as the key and digest as the value.

@return [Hash] hash of digests with reference uri as the key

# File lib/sepa/response.rb, line 228
def find_digest_values
  references = {}
  reference_nodes = doc.css('xmlns|Reference', xmlns: DSIG)

  reference_nodes.each do |node|
    uri = node.attr('URI')
    digest_value = node.at('xmlns|DigestValue', xmlns: DSIG).content

    references[uri] = digest_value
  end

  references
end
find_node_by_uri(uri) click to toggle source

Find node by it's reference URI in soap header

@param uri [String] the node's URI @return [Nokogiri::XML::Node]

# File lib/sepa/response.rb, line 290
def find_node_by_uri(uri)
  doc.at("[xmlns|Id='#{uri}']", xmlns: OASIS_UTILITY)
end
find_nodes_to_verify(references) click to toggle source

Finds nodes to verify by comparing their id's to the uris' in the references hash. Then calculates the hashes of those nodes and returns them in a hash

@param references [Hash] @return [Hash] hash of calculated digests with reference uri as the key

# File lib/sepa/response.rb, line 247
def find_nodes_to_verify(references)
  nodes = {}

  references.each do |uri, _digest_value|
    uri = uri.sub(/^#/, '')
    node = find_node_by_uri(uri)

    nodes[uri] = calculate_digest(node)
  end

  nodes
end
response_code_is_ok?() click to toggle source

Checks whether response code in the response is ok. Response code is considered ok if it is “00” or “24”.

@return [true] if response code is ok @return [false] if response code is not ok

# File lib/sepa/response.rb, line 331
def response_code_is_ok?
  return true if %w(00 24).include? response_code

  false
end
validate_hashes() click to toggle source

Validates hashes in the response. {#hashes_match?} must return true for validation to pass. Is not run if {#error} is present or response code is not ok.

# File lib/sepa/response.rb, line 303
def validate_hashes
  return if @error || !response_code_is_ok? || hashes_match?

  errors.add(:base, HASH_ERROR_MESSAGE)
end
validate_response_code() click to toggle source

Validates response code in response. “00” and “24” are currently considered valid.

# File lib/sepa/response.rb, line 295
def validate_response_code
  return if %w(00 24).include? response_code

  errors.add(:base, response_code: response_code, response_text: response_text)
end
verify_certificate() click to toggle source

Validates certificate in the soap. The certificate must be present and signed by the bank's root certificate for the validation to pass. Is not run if {#error} is present or response code is not ok.

# File lib/sepa/response.rb, line 320
def verify_certificate
  return if @error || !response_code_is_ok? || certificate_is_trusted?

  errors.add(:base, 'The certificate in the response is not trusted')
end
verify_signature() click to toggle source

Validate signature in the response. Validation is not run if {#error} is present or response is not ok.

# File lib/sepa/response.rb, line 311
def verify_signature
  return if @error || !response_code_is_ok? || signature_is_valid?

  errors.add(:base, SIGNATURE_ERROR_MESSAGE)
end