hbci 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +26 -0
- data/.rubocop_todo.yml +26 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/examples/credentials.rb +62 -0
- data/examples/get_accounts.rb +11 -0
- data/examples/get_balance.rb +8 -0
- data/examples/get_system_id.rb +6 -0
- data/examples/get_transactions.rb +15 -0
- data/hbci.gemspec +36 -0
- data/lib/development_profiler.rb +26 -0
- data/lib/hbci.rb +66 -0
- data/lib/hbci/connector.rb +33 -0
- data/lib/hbci/dialog.rb +77 -0
- data/lib/hbci/element_group.rb +93 -0
- data/lib/hbci/element_groups/generated_element_groups.rb +969 -0
- data/lib/hbci/element_groups/segment_head.rb +12 -0
- data/lib/hbci/element_groups/unknown.rb +11 -0
- data/lib/hbci/element_unparser.rb +21 -0
- data/lib/hbci/message.rb +47 -0
- data/lib/hbci/message_factory.rb +17 -0
- data/lib/hbci/parser.rb +114 -0
- data/lib/hbci/request.rb +32 -0
- data/lib/hbci/response.rb +25 -0
- data/lib/hbci/segment.rb +118 -0
- data/lib/hbci/segment_factory.rb +15 -0
- data/lib/hbci/segments/hikaz.rb +29 -0
- data/lib/hbci/segments/hikazs.rb +31 -0
- data/lib/hbci/segments/hirmg.rb +10 -0
- data/lib/hbci/segments/hirms.rb +17 -0
- data/lib/hbci/segments/hisal.rb +100 -0
- data/lib/hbci/segments/hisals.rb +19 -0
- data/lib/hbci/segments/hisyn.rb +11 -0
- data/lib/hbci/segments/hiupa.rb +12 -0
- data/lib/hbci/segments/hiupd.rb +37 -0
- data/lib/hbci/segments/hkend.rb +14 -0
- data/lib/hbci/segments/hkidn.rb +22 -0
- data/lib/hbci/segments/hkkaz.rb +25 -0
- data/lib/hbci/segments/hksal.rb +31 -0
- data/lib/hbci/segments/hksyn.rb +12 -0
- data/lib/hbci/segments/hkvvb.rb +18 -0
- data/lib/hbci/segments/hnhbk.rb +27 -0
- data/lib/hbci/segments/hnhbs.rb +14 -0
- data/lib/hbci/segments/hnsha.rb +31 -0
- data/lib/hbci/segments/hnshk.rb +79 -0
- data/lib/hbci/segments/hnvsd.rb +50 -0
- data/lib/hbci/segments/hnvsk.rb +60 -0
- data/lib/hbci/segments/segment_head.rb +11 -0
- data/lib/hbci/segments/unknown.rb +21 -0
- data/lib/hbci/services/accounts_receiver.rb +19 -0
- data/lib/hbci/services/balance_receiver.rb +69 -0
- data/lib/hbci/services/base_receiver.rb +43 -0
- data/lib/hbci/services/system_id_receiver.rb +35 -0
- data/lib/hbci/services/transactions_receiver.rb +75 -0
- data/lib/hbci/version.rb +5 -0
- metadata +315 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class ElementUnparser
|
5
|
+
attr_reader :element
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize(element, type)
|
9
|
+
@element = element
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def unparse
|
14
|
+
if type == :binary
|
15
|
+
"@#{element.size}@#{element}"
|
16
|
+
else
|
17
|
+
element.to_s.gsub('?', '??').gsub('+', '?+').gsub(':', '?:').gsub('\'', '?\'')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/hbci/message.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Message
|
5
|
+
attr_reader :dialog, :segments, :sec_ref
|
6
|
+
attr_accessor :next_position
|
7
|
+
|
8
|
+
def initialize(dialog = nil)
|
9
|
+
@dialog = dialog
|
10
|
+
@sec_ref = generate_security_reference
|
11
|
+
@segments = []
|
12
|
+
@next_position = 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_segment(segment)
|
16
|
+
segment.build(self)
|
17
|
+
@segments.push(segment)
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
@segments.each_with_index do |segment, index|
|
22
|
+
segment.compile
|
23
|
+
unless segment.head.position
|
24
|
+
segment.head.position = @next_position
|
25
|
+
@next_position += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@segments.each do |segment|
|
29
|
+
segment.after_compile if segment.respond_to?(:after_compile)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
segments.join('')
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_base64
|
38
|
+
Base64.encode64(to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def generate_security_reference
|
44
|
+
rand(1..23) * 999_999 + 1_000_000
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Hbci
|
2
|
+
module MessageFactory
|
3
|
+
def self.build(dialog)
|
4
|
+
request_message = Message.new(dialog)
|
5
|
+
request_message.add_segment(Segments::HNHBKv3.new)
|
6
|
+
request_message.add_segment(Segments::HNVSKv3.new)
|
7
|
+
hnvsd = Segments::HNVSDv1.new do |s|
|
8
|
+
s.add_segment(Segments::HNSHKv4.new)
|
9
|
+
yield s
|
10
|
+
s.add_segment(Segments::HNSHAv2.new)
|
11
|
+
end
|
12
|
+
request_message.add_segment(hnvsd)
|
13
|
+
request_message.add_segment(Segments::HNHBSv1.new)
|
14
|
+
request_message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/hbci/parser.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Parser
|
5
|
+
attr_reader :scanner
|
6
|
+
attr_accessor :segments
|
7
|
+
|
8
|
+
ELEMENT_DELIMITER = ':'
|
9
|
+
ELEMENT_GROUP_DELIMITER = '+'
|
10
|
+
SEGMENT_DELIMITER = '\''
|
11
|
+
|
12
|
+
# ELEMENT_REGEX matches everything until a a delimiter that is not escaped
|
13
|
+
#
|
14
|
+
# NODE EXPLANATION
|
15
|
+
# ------------------------------------------------------------------------
|
16
|
+
# ( group and capture to \1:
|
17
|
+
# ------------------------------------------------------------------------
|
18
|
+
# .*? any character except \n (0 or more times
|
19
|
+
# (matching the least amount possible))
|
20
|
+
# ------------------------------------------------------------------------
|
21
|
+
# (?= look ahead to see if there is:
|
22
|
+
# ------------------------------------------------------------------------
|
23
|
+
# (?<! look behind to see if there is not:
|
24
|
+
# ------------------------------------------------------------------------
|
25
|
+
# \? '?'
|
26
|
+
# ------------------------------------------------------------------------
|
27
|
+
# ) end of look-behind
|
28
|
+
# ------------------------------------------------------------------------
|
29
|
+
# [:+'] any character of: ':', '+', '''
|
30
|
+
# ------------------------------------------------------------------------
|
31
|
+
# ) end of look-ahead
|
32
|
+
# ------------------------------------------------------------------------
|
33
|
+
# ) end of \1
|
34
|
+
ELEMENT_REGEX = /(.*?(?=(?<!\?)[:+']))/
|
35
|
+
|
36
|
+
# Binary Elements may contain unescaped delimiters. Thus they are not
|
37
|
+
# terminated by regular delimiters. But their content is preceeded with
|
38
|
+
# its length surrounded by '@'s. e.g.:
|
39
|
+
#
|
40
|
+
# '@6@mydata' or '@12@mydatamydata'
|
41
|
+
#
|
42
|
+
# The BINARY_ELEMENT_LENGTH_REGEx matches only the length.
|
43
|
+
BINARY_ELEMENT_LENGTH_REGEX = /@(\d+)@/
|
44
|
+
|
45
|
+
def self.parse(string)
|
46
|
+
new(string).parse
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(string)
|
50
|
+
@scanner = StringScanner.new(string)
|
51
|
+
@segments = []
|
52
|
+
add_segment
|
53
|
+
add_element_group
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse
|
57
|
+
parse_element
|
58
|
+
while scanner.rest_size > 1
|
59
|
+
parse_delimiter
|
60
|
+
parse_element
|
61
|
+
end
|
62
|
+
segments
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def parse_regular_element
|
68
|
+
str = scanner.scan(ELEMENT_REGEX)
|
69
|
+
current_element_group << str.gsub('??', '?').gsub('?:', ':').gsub("?'", "'").gsub('?+', '+')
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_element
|
73
|
+
binary_element_ahead? ? parse_binary_element : parse_regular_element
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_delimiter
|
77
|
+
delimiter = scanner.getch
|
78
|
+
return if scanner.eos?
|
79
|
+
if delimiter == ELEMENT_GROUP_DELIMITER
|
80
|
+
add_element_group
|
81
|
+
elsif delimiter == SEGMENT_DELIMITER
|
82
|
+
add_segment
|
83
|
+
add_element_group
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_binary_element
|
88
|
+
scanner.scan(BINARY_ELEMENT_LENGTH_REGEX)
|
89
|
+
binary = scanner.rest.byteslice(0, scanner[1].to_i)
|
90
|
+
scanner.pos = scanner.pos + scanner[1].to_i
|
91
|
+
current_element_group << binary
|
92
|
+
end
|
93
|
+
|
94
|
+
def binary_element_ahead?
|
95
|
+
scanner.peek(1) == '@' && scanner.check(BINARY_ELEMENT_LENGTH_REGEX)
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_segment
|
99
|
+
segments << []
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_element_group
|
103
|
+
current_segment << []
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_segment
|
107
|
+
segments.last
|
108
|
+
end
|
109
|
+
|
110
|
+
def current_element_group
|
111
|
+
current_segment.last
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/hbci/request.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Request < Message
|
5
|
+
attr_reader :dialog
|
6
|
+
attr_reader :sec_ref
|
7
|
+
|
8
|
+
def head
|
9
|
+
@head ||= Segments::HNHBKv3.build(dialog: dialog, message: self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def enc_head
|
13
|
+
@enc_head ||= Segments::HNVSKv3.build(dialog: dialog)
|
14
|
+
end
|
15
|
+
|
16
|
+
def sig_head
|
17
|
+
@sig_head ||= Segments::HNSHKv4.build(dialog: dialog, message: self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def encrypted_payload
|
21
|
+
@encrypted_payload ||= Segments::HNVSDv1.build(message: self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sig_tail
|
25
|
+
@sig_tail ||= Segments::HNSHAv2.build(dialog: dialog, message: self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tail
|
29
|
+
@tail ||= Segments::HNHBSv1.build(dialog: dialog, message: self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Hbci
|
5
|
+
class Response
|
6
|
+
def initialize(raw_response)
|
7
|
+
@raw_response = raw_response
|
8
|
+
@raw_segments = Parser.parse(raw_response.force_encoding('iso-8859-1'))
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(segment_type)
|
12
|
+
segments = find_all(segment_type)
|
13
|
+
warn "more then one #{segment_type} segment available" if segments.size > 1
|
14
|
+
segments.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_all(segment_type)
|
18
|
+
@raw_segments.select { |sd| sd[0][0] == segment_type }.map{ |sd| Hbci::SegmentFactory.build(sd) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@raw_response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/hbci/segment.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Segment
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :element_groups
|
8
|
+
attr_reader :defined_element_groups
|
9
|
+
attr_accessor :message
|
10
|
+
attr_accessor :dialog
|
11
|
+
|
12
|
+
attr_accessor :request_message
|
13
|
+
|
14
|
+
def self.element_groups_to_be_defined
|
15
|
+
@element_groups_to_be_defined ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.element_group(name, definition = {}, &block)
|
19
|
+
element_groups_to_be_defined << definition.merge(name: name, block: block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.element(name, definition = {})
|
23
|
+
element_groups_to_be_defined << definition.merge(name: name, block: nil, passthrough: true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build(message)
|
27
|
+
self.request_message = message
|
28
|
+
|
29
|
+
type, version = self.class.name.split('::').last.split('v')
|
30
|
+
|
31
|
+
head.type = type
|
32
|
+
head.version = version
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.fill(segment_data)
|
36
|
+
segment_data.each_with_object(new).with_index do |(element_group_data, segment), element_group_index|
|
37
|
+
element_group_data.each_with_index do |element_data, element_index|
|
38
|
+
segment.element_groups[element_group_index][element_index] = element_data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@element_groups = []
|
45
|
+
@defined_element_groups ||= []
|
46
|
+
define_element_groups
|
47
|
+
end
|
48
|
+
|
49
|
+
def compile
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
element_groups.join('+').gsub(/\+*$/, '') << '\''
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def define_element_groups
|
59
|
+
self.class.element_groups_to_be_defined.each { |element_group| define_element_group(element_group) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def index_of_element_group(name)
|
63
|
+
defined_element_groups.index(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_element_group(definition)
|
67
|
+
defined_element_groups << definition[:name]
|
68
|
+
|
69
|
+
if definition[:passthrough]
|
70
|
+
initiate_passthrough_element_group(definition)
|
71
|
+
define_element_group_passthrough_reader(definition[:name])
|
72
|
+
define_element_group_passthrough_writer(definition[:name])
|
73
|
+
else
|
74
|
+
initiate_element_group(definition)
|
75
|
+
define_element_group_reader(definition[:name])
|
76
|
+
define_element_group_writer(definition[:name])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def initiate_passthrough_element_group(definition)
|
81
|
+
element_group = Hbci::ElementGroup.new
|
82
|
+
element_group.define_element(definition)
|
83
|
+
element_groups[index_of_element_group(definition[:name])] = element_group
|
84
|
+
end
|
85
|
+
|
86
|
+
def initiate_element_group(definition)
|
87
|
+
element_group = definition[:type] ? definition[:type].new : Hbci::ElementGroup.new
|
88
|
+
element_group.instance_eval(&definition[:block]) if definition[:block]
|
89
|
+
element_groups[index_of_element_group(definition[:name])] = element_group
|
90
|
+
end
|
91
|
+
|
92
|
+
def define_element_group_passthrough_reader(name)
|
93
|
+
define_singleton_method(name.to_s) do
|
94
|
+
element_groups[index_of_element_group(name)][0]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_element_group_reader(name)
|
99
|
+
define_singleton_method(name.to_s) do
|
100
|
+
element_groups[index_of_element_group(name)]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_element_group_writer(name)
|
105
|
+
define_singleton_method("#{name}=") do |value|
|
106
|
+
element_groups[index_of_element_group(name)] = value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def define_element_group_passthrough_writer(name)
|
111
|
+
define_singleton_method("#{name}=") do |value|
|
112
|
+
element_groups[index_of_element_group(name)][0] = value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def after_build; end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class SegmentFactory
|
5
|
+
def self.build(segment_data)
|
6
|
+
segment_class_name = "#{segment_data[0][0]}v#{segment_data[0][2]}"
|
7
|
+
segment_class = begin
|
8
|
+
Object.const_get("Hbci::Segments::#{segment_class_name}")
|
9
|
+
rescue StandardError
|
10
|
+
Hbci::Segments::Unknown
|
11
|
+
end
|
12
|
+
segment_class.fill(segment_data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
class HIKAZv4 < Segment
|
6
|
+
element_group :head, type: ElementGroups::SegmentHead
|
7
|
+
element :booked
|
8
|
+
element :notbooked
|
9
|
+
end
|
10
|
+
|
11
|
+
class HIKAZv5 < Segment
|
12
|
+
element_group :head, type: ElementGroups::SegmentHead
|
13
|
+
element :booked
|
14
|
+
element :notbooked
|
15
|
+
end
|
16
|
+
|
17
|
+
class HIKAZv6 < Segment
|
18
|
+
element_group :head, type: ElementGroups::SegmentHead
|
19
|
+
element :booked
|
20
|
+
element :notbooked
|
21
|
+
end
|
22
|
+
|
23
|
+
class HIKAZv7 < Segment
|
24
|
+
element_group :head, type: ElementGroups::SegmentHead
|
25
|
+
element :booked
|
26
|
+
element :notbooked
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|