hbci 0.2.0

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +26 -0
  4. data/.rubocop_todo.yml +26 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +1 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +107 -0
  10. data/Rakefile +8 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +7 -0
  13. data/examples/credentials.rb +62 -0
  14. data/examples/get_accounts.rb +11 -0
  15. data/examples/get_balance.rb +8 -0
  16. data/examples/get_system_id.rb +6 -0
  17. data/examples/get_transactions.rb +15 -0
  18. data/hbci.gemspec +36 -0
  19. data/lib/development_profiler.rb +26 -0
  20. data/lib/hbci.rb +66 -0
  21. data/lib/hbci/connector.rb +33 -0
  22. data/lib/hbci/dialog.rb +77 -0
  23. data/lib/hbci/element_group.rb +93 -0
  24. data/lib/hbci/element_groups/generated_element_groups.rb +969 -0
  25. data/lib/hbci/element_groups/segment_head.rb +12 -0
  26. data/lib/hbci/element_groups/unknown.rb +11 -0
  27. data/lib/hbci/element_unparser.rb +21 -0
  28. data/lib/hbci/message.rb +47 -0
  29. data/lib/hbci/message_factory.rb +17 -0
  30. data/lib/hbci/parser.rb +114 -0
  31. data/lib/hbci/request.rb +32 -0
  32. data/lib/hbci/response.rb +25 -0
  33. data/lib/hbci/segment.rb +118 -0
  34. data/lib/hbci/segment_factory.rb +15 -0
  35. data/lib/hbci/segments/hikaz.rb +29 -0
  36. data/lib/hbci/segments/hikazs.rb +31 -0
  37. data/lib/hbci/segments/hirmg.rb +10 -0
  38. data/lib/hbci/segments/hirms.rb +17 -0
  39. data/lib/hbci/segments/hisal.rb +100 -0
  40. data/lib/hbci/segments/hisals.rb +19 -0
  41. data/lib/hbci/segments/hisyn.rb +11 -0
  42. data/lib/hbci/segments/hiupa.rb +12 -0
  43. data/lib/hbci/segments/hiupd.rb +37 -0
  44. data/lib/hbci/segments/hkend.rb +14 -0
  45. data/lib/hbci/segments/hkidn.rb +22 -0
  46. data/lib/hbci/segments/hkkaz.rb +25 -0
  47. data/lib/hbci/segments/hksal.rb +31 -0
  48. data/lib/hbci/segments/hksyn.rb +12 -0
  49. data/lib/hbci/segments/hkvvb.rb +18 -0
  50. data/lib/hbci/segments/hnhbk.rb +27 -0
  51. data/lib/hbci/segments/hnhbs.rb +14 -0
  52. data/lib/hbci/segments/hnsha.rb +31 -0
  53. data/lib/hbci/segments/hnshk.rb +79 -0
  54. data/lib/hbci/segments/hnvsd.rb +50 -0
  55. data/lib/hbci/segments/hnvsk.rb +60 -0
  56. data/lib/hbci/segments/segment_head.rb +11 -0
  57. data/lib/hbci/segments/unknown.rb +21 -0
  58. data/lib/hbci/services/accounts_receiver.rb +19 -0
  59. data/lib/hbci/services/balance_receiver.rb +69 -0
  60. data/lib/hbci/services/base_receiver.rb +43 -0
  61. data/lib/hbci/services/system_id_receiver.rb +35 -0
  62. data/lib/hbci/services/transactions_receiver.rb +75 -0
  63. data/lib/hbci/version.rb +5 -0
  64. metadata +315 -0
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hbci
4
+ module ElementGroups
5
+ class SegmentHead < ElementGroup
6
+ element :type
7
+ element :position
8
+ element :version
9
+ element :reference
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hbci
4
+ module ElementGroups
5
+ class Unknown < ElementGroup
6
+ 99.times do |i|
7
+ element :"entry_#{i}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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