Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement basic support for distributed signatures #39

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions lib/epics/client.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
require_relative 'h004'
require_relative './hvu'
require_relative './hvz'
require_relative './hvd'
require_relative './hve'
require_relative './hvs'

class Epics::Client
extend Forwardable

Expand Down Expand Up @@ -151,6 +158,7 @@ def HAA
Nokogiri::XML(download(Epics::HAA)).at_xpath("//xmlns:OrderTypes", xmlns: "urn:org:ebics:H004").content.split(/\s/)
end

# fetch client and subscriber data
def HTD
Nokogiri::XML(download(Epics::HTD)).tap do |htd|
@iban ||= htd.at_xpath("//xmlns:AccountNumber[@international='true']", xmlns: "urn:org:ebics:H004").text
Expand All @@ -159,34 +167,67 @@ def HTD
end.to_xml
end

# fetch bank parameters
def HPD
download(Epics::HPD)
end

# fetch client and subscriber data
def HKD
download(Epics::HKD)
end

# fetch text protocol
def PTK(from, to)
download(Epics::PTK, from, to)
end

# XML Protokolldatei abholen
def HAC(from = nil, to = nil)
download(Epics::HAC, from, to)
end

# VEU related actions

# Fetch overview of all orders which need to be signed
def HVU
Epics::H004.from_xml(download(Epics::HVU)).to_h
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need this intermediate class Epics::H004 which determines the type and reacts accordingly? I mean in this method we already know that the result will be HVU anyways.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could refactor it to something like:

def download(klass) 
  res = ... do all the things
  klass.response.new(res)
end

class Klass
  def headers
   ....
   end

  def response
    KlassRresponse
  end
end
class KlassResponse
  def initialize(res)
   // do whatever
  end
  def to_h
  end 
end

end

# Fetch detailed overview of all orders which need to be signed
def HVZ
Epics::H004.from_xml(download(Epics::HVZ)).to_h
end

# Fetch details for an order
def HVD(order_id, order_type)
Epics::H004.from_xml(download(Epics::HVD, order_id, order_type)).to_h
end

# sign an order
def HVE(order_id, order_type, digest)
upload(Epics::HVE, order_id, order_type, digest)
end

# reject an order
def HVS(order_id, order_type, digest)
upload(Epics::HVS, order_id, order_type, digest)
end

def save_keys(path)
File.write(path, dump_keys)
end

private

def upload(order_type, document)
order = order_type.new(self, document)
def upload(order_type, *args)
order = order_type.new(self, *args)
res = post(url, order.to_xml).body
order.transaction_id = res.transaction_id

res = post(url, order.to_transfer_xml).body
if order.segmented?
res = post(url, order.to_transfer_xml).body
end

return res.transaction_id, res.order_id
end
Expand Down
8 changes: 6 additions & 2 deletions lib/epics/generic_upload_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ class Epics::GenericUploadRequest < Epics::GenericRequest
attr_accessor :key
attr_accessor :document

def initialize(client, document)
def initialize(client, document = nil)
super(client)
self.document = document
self.key ||= cipher.random_key
end

def segmented?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you choose segmented as name? Kinda reminds me of segmented downloads

true
end

def cipher
@cipher ||= OpenSSL::Cipher.new("aes-128-cbc")
end
Expand Down Expand Up @@ -83,4 +87,4 @@ def pad(d)
end
end

end
end
30 changes: 30 additions & 0 deletions lib/epics/h004.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'nokogiri'

require_relative './h004/hvu'
require_relative './h004/hvz'
require_relative './h004/hvd'

module Epics
module H004
UnknownInput = Class.new(ArgumentError)

def self.from_xml(raw_xml)
doc = Nokogiri::XML(raw_xml).at_xpath('/*')

unless doc.namespace.href == "urn:org:ebics:H004"
fail UnknownInput, "Unknown xml file contents"
end

case doc.name
when "HVZResponseOrderData"
Epics::H004::HVZ.new(doc)
when "HVUResponseOrderData"
Epics::H004::HVU.new(doc)
when "HVDResponseOrderData"
Epics::H004::HVD.new(doc)
end
rescue Nokogiri::XML::XPath::SyntaxError => ex
fail UnknownInput, "Invalid XML input data: #{ex.message}"
end
end
end
46 changes: 46 additions & 0 deletions lib/epics/h004/hvd.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Epics
module H004
class HVD
attr_accessor :doc

def initialize(xml_doc)
self.doc = xml_doc
end

def to_h
order = doc.at_xpath("/h004:HVDResponseOrderData")
{
signers: signers(order.xpath('./h004:SignerInfo')),
order_details_available: order.at_xpath('./h004:OrderDetailsAvailable').content == 'true',
order_data_available: order.at_xpath('./h004:OrderDataAvailable').content == 'true',
digest: order.at_xpath('./h004:DataDigest').content,
digest_signature_version: order.at_xpath('./h004:DataDigest')['SignatureVersion'],
display_file: Base64.decode64(order.at_xpath('./h004:DisplayFile').content).encode("UTF-8", "ISO-8859-15"),
}
end

private

def originator(node)
{
name: node.at_xpath('./h004:Name').content.strip,
partner_id: node.at_xpath('./h004:PartnerID').content.strip,
user_id: node.at_xpath('./h004:UserID').content.strip,
timestamp: Time.parse(node.at_xpath('./h004:Timestamp').content),
}
end

def signers(nodes)
nodes.map do |signer|
{
name: signer.at_xpath('./h004:Name').content.strip,
partner_id: signer.at_xpath('./h004:PartnerID').content.strip,
user_id: signer.at_xpath('./h004:UserID').content.strip,
signature_class: signer.at_xpath('./h004:Permission')['AuthorisationLevel']
}
end
end

end
end
end
48 changes: 48 additions & 0 deletions lib/epics/h004/hvu.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Epics
module H004
class HVU
attr_accessor :doc

def initialize(xml_doc)
self.doc = xml_doc
end

def to_h
doc.xpath("/h004:HVUResponseOrderData/h004:OrderDetails").map do |order|
{
order_id: order.at_xpath('./h004:OrderID').content,
order_type: order.at_xpath('./h004:OrderType').content,
originator: originator(order.at_xpath('./h004:OriginatorInfo')),
signers: signers(order.xpath('./h004:SignerInfo')),
required_signatures: order.at_xpath('./h004:SigningInfo')['NumSigRequired'].to_i,
applied_signatures: order.at_xpath('./h004:SigningInfo')['NumSigDone'].to_i,
ready_for_signature: order.at_xpath('./h004:SigningInfo')['readyToBeSigned'].to_s.downcase == 'true',
}
end
end

private

def originator(node)
{
name: node.at_xpath('./h004:Name').content.strip,
partner_id: node.at_xpath('./h004:PartnerID').content.strip,
user_id: node.at_xpath('./h004:UserID').content.strip,
timestamp: Time.parse(node.at_xpath('./h004:Timestamp').content),
}
end

def signers(nodes)
nodes.map do |signer|
{
name: signer.at_xpath('./h004:Name').content.strip,
partner_id: signer.at_xpath('./h004:PartnerID').content.strip,
user_id: signer.at_xpath('./h004:UserID').content.strip,
signature_class: signer.at_xpath('./h004:Permission')['AuthorisationLevel']
}
end
end

end
end
end
58 changes: 58 additions & 0 deletions lib/epics/h004/hvz.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require 'bigdecimal'

module Epics
module H004
class HVZ
attr_accessor :doc

def initialize(xml_doc)
self.doc = xml_doc
end

def to_h
doc.xpath("/h004:HVZResponseOrderData/h004:OrderDetails").map do |order|
{
order_id: order.at_xpath('./h004:OrderID').content,
order_type: order.at_xpath('./h004:OrderType').content,
originator: originator(order.at_xpath('./h004:OriginatorInfo')),
signers: signers(order.xpath('./h004:SignerInfo')),
required_signatures: order.at_xpath('./h004:SigningInfo')['NumSigRequired'].to_i,
applied_signatures: order.at_xpath('./h004:SigningInfo')['NumSigDone'].to_i,
ready_for_signature: as_boolean(order.at_xpath('./h004:SigningInfo')['readyToBeSigned']),
total_amount: BigDecimal.new(order.at_xpath('./h004:TotalAmount').content),
total_amount_type: as_boolean(order.at_xpath('./h004:TotalAmount')['isCredit']) ? 'credit' : 'debit',
total_orders: order.at_xpath('./h004:TotalOrders').content.to_i,
digest: order.at_xpath('./h004:DataDigest').content,
digest_signature_version: order.at_xpath('./h004:DataDigest')['SignatureVersion'],
}
end
end

private

def originator(node)
{
name: node.at_xpath('./h004:Name').content.strip,
partner_id: node.at_xpath('./h004:PartnerID').content.strip,
user_id: node.at_xpath('./h004:UserID').content.strip,
timestamp: Time.parse(node.at_xpath('./h004:Timestamp').content),
}
end

def signers(nodes)
nodes.map do |signer|
{
name: signer.at_xpath('./h004:Name').content.strip,
partner_id: signer.at_xpath('./h004:PartnerID').content.strip,
user_id: signer.at_xpath('./h004:UserID').content.strip,
signature_class: signer.at_xpath('./h004:Permission')['AuthorisationLevel']
}
end
end

def as_boolean(input)
input.to_s.downcase == 'true'
end
end
end
end
53 changes: 53 additions & 0 deletions lib/epics/hvd.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Epics
class HVD < Epics::GenericRequest
attr_accessor :order_id, :order_type

def initialize(client, order_id, order_type)
super(client)
self.order_id = order_id
self.order_type = order_type
end

def header
{
:@authenticate => true,
static: {
"HostID" => host_id,
"Nonce" => nonce,
"Timestamp" => timestamp,
"PartnerID" => partner_id,
"UserID" => user_id,
"Product" => {
:@Language => "de",
:content! => "EPICS - a ruby ebics kernel"
},
"OrderDetails" => {
"OrderType" => "HVD",
"OrderAttribute" => "DZHNN",
"HVDOrderParams" => {
"PartnerID" => partner_id,
"OrderType" => order_type,
"OrderID" => order_id,
}
},
"BankPubKeyDigests" => {
"Authentication" => {
:@Version => "X002",
:@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
:content! => client.bank_x.public_digest
},
"Encryption" => {
:@Version => "E002",
:@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
:content! => client.bank_e.public_digest
}
},
"SecurityMedium" => "0000"
},
"mutable" => {
"TransactionPhase" => "Initialisation"
}
}
end
end
end
Loading