hbci 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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