ibanity 1.0.1 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 750cf05359ce3376249cd8c3854adc897728ee99905539f959ae3991295dca9d
4
- data.tar.gz: 5b3fb64906c906678ba6c9d4de8a017e47129e7db49d93da17abdd822e48e96e
3
+ metadata.gz: 8764b1c890efb8ca4d619b84264ba4fcd0a10cdbd6d1fd505da2b733662d329c
4
+ data.tar.gz: a44447ecf5aec8045ade36f08702f8a134f98d5eff010843ba730902dfd0552e
5
5
  SHA512:
6
- metadata.gz: 80171748c7786074541ec03a8cb29e375e88cd47ad420f1315f2241b45d067ec7a2fc015e426eee8ff2c020154c592d7dceb3f47c473276fa4ebd2aa0ab1ef64
7
- data.tar.gz: 98bbc8f9ee5cf3b879dfd744f97bc27008098d2a5506d16bcbec18151ecfe67386545ad8d5b9bd625dd0d84b3f26fe6672c22e0f36798eb9632ab29cac055ec9
6
+ metadata.gz: 8a30ada191b9256e197c851ff8c09dd9cfc89c3b72ac28c0c726da288dd925868c944f7f461cd40af55a28499ae7581718e2ef4a471dd36531e312b776c7fca1
7
+ data.tar.gz: 94d709de117a166321a587cf5db08b2fea8657547fc4a3dd1cf2844c68db4d9fa63d72ed3a2d56f43639c31400831ab611ac6081d545835af2146bfd09f60248
data/.gitignore CHANGED
@@ -16,3 +16,6 @@ mkmf.log
16
16
  .ruby*
17
17
  *.gem
18
18
  *.env
19
+ /.idea
20
+ /bin
21
+ .rakeTasks
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -8,3 +8,13 @@ deploy:
8
8
  tags: true
9
9
  gem: ibanity
10
10
  skip_cleanup: true
11
+ notifications:
12
+ email: false
13
+ slack:
14
+ - rooms:
15
+ - secure: J06CK+muuVTW1SS1GmhSw7ZVo0vEMFpNWU5B9Cz94h5BZI3rPSxZFSCQG8OTQ/lBIrzPTlQj5JGzYgHpS5QRHGa4s1A1tYrZUfjUUgaZ7SIfoY8AgHH6cKF+jtwYy2z+kOJekBtfNLqfzAx+48QrxdbmBnXTG+r7UXczw0TAB7BMKh8107q7kqgjKiAnpGlXzC6rLWUwdRF/1qvDtYFCjoZ0OpYMnUUK+0/bgwVHxjYRl64DrPD3jC2XoaCTRCMyLbfqb9ZUXKVf27ByAQ/Ikj+qODdSH10vwPARXlbbVhpPfjAax42zC1OwSABdE00iJW1+TuoI69znjWg1d8Ti97raihV98OXMyqHXPKxX/gZp+0MSuYIE9GQWY791ulmQ+GwwkgB6v2LmXGCE8K+eUCc3zkGJWVUzBP+Wu49mDzDAQHBPl/G3u26RqmVQzWhk9ckHQ30cyIPFjz/eaMrda+wgQ6AKzfNq+XVL6hAO+Se28PRPhDu33ZzIKqgpk0dH77LcKPI/Uf1dCT+0oa+pvVx1T0Z93SNp0azh71rx9AOWBMZm7TcOYvMOw9QpVBZkoWy4NQo9PMT0Jo73qAnOTHadr8b5zeDoAu7jnlhCxIpmnrmIh4Dz6n6XF+c+d+C/MAQVQ0rKj7KzTYEsYat0NQ62lgBtHy5/Qmu736MaIVA=
16
+ if: (tag =~ ^v) AND (branch = master)
17
+ - rooms:
18
+ - secure: J06CK+muuVTW1SS1GmhSw7ZVo0vEMFpNWU5B9Cz94h5BZI3rPSxZFSCQG8OTQ/lBIrzPTlQj5JGzYgHpS5QRHGa4s1A1tYrZUfjUUgaZ7SIfoY8AgHH6cKF+jtwYy2z+kOJekBtfNLqfzAx+48QrxdbmBnXTG+r7UXczw0TAB7BMKh8107q7kqgjKiAnpGlXzC6rLWUwdRF/1qvDtYFCjoZ0OpYMnUUK+0/bgwVHxjYRl64DrPD3jC2XoaCTRCMyLbfqb9ZUXKVf27ByAQ/Ikj+qODdSH10vwPARXlbbVhpPfjAax42zC1OwSABdE00iJW1+TuoI69znjWg1d8Ti97raihV98OXMyqHXPKxX/gZp+0MSuYIE9GQWY791ulmQ+GwwkgB6v2LmXGCE8K+eUCc3zkGJWVUzBP+Wu49mDzDAQHBPl/G3u26RqmVQzWhk9ckHQ30cyIPFjz/eaMrda+wgQ6AKzfNq+XVL6hAO+Se28PRPhDu33ZzIKqgpk0dH77LcKPI/Uf1dCT+0oa+pvVx1T0Z93SNp0azh71rx9AOWBMZm7TcOYvMOw9QpVBZkoWy4NQo9PMT0Jo73qAnOTHadr8b5zeDoAu7jnlhCxIpmnrmIh4Dz6n6XF+c+d+C/MAQVQ0rKj7KzTYEsYat0NQ62lgBtHy5/Qmu736MaIVA=
19
+ on_success: change
20
+ on_failure: always
@@ -1,6 +1,28 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.1
3
+ ## 1.5
4
+
5
+ * Proper release of previous enhancements
6
+
7
+ ## 1.4
8
+
9
+ *Don't use this version as it was not properly released !*
10
+
11
+ ## 1.3
12
+
13
+ ### Enhancements
14
+
15
+ * [Ponto Connect] Add support for the /userinfo and /organizations/{id}/usage endpoints.
16
+
17
+ ## 1.2
18
+
19
+ ### Enhancements
20
+
21
+ * Default signature algorithm is now ["hs2019"](https://tools.ietf.org/html/draft-cavage-http-signatures-12#appendix-E.2)
22
+ * Add support for periodic and bulk payments
23
+ * Add snake-case transformation for deeply nested data structures
24
+
25
+ ## 1.1.1
4
26
 
5
27
  ### Enhancements
6
28
 
@@ -19,6 +19,5 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "rest-client", ">= 1.8.0"
22
- spec.add_development_dependency "rspec", "3.4.0"
23
- spec.add_development_dependency "webmock", "1.24.2"
22
+ spec.add_development_dependency "rspec", "3.9.0"
24
23
  end
@@ -21,6 +21,8 @@ require_relative "ibanity/api/xs2a/payment_initiation_request_authorization"
21
21
  require_relative "ibanity/api/xs2a/customer_access_token"
22
22
  require_relative "ibanity/api/xs2a/customer"
23
23
  require_relative "ibanity/api/xs2a/payment_initiation_request"
24
+ require_relative "ibanity/api/xs2a/bulk_payment_initiation_request"
25
+ require_relative "ibanity/api/xs2a/periodic_payment_initiation_request"
24
26
  require_relative "ibanity/api/xs2a/synchronization"
25
27
  require_relative "ibanity/api/o_auth_resource"
26
28
  require_relative "ibanity/api/isabel_connect/account"
@@ -43,6 +45,10 @@ require_relative "ibanity/api/ponto_connect/synchronization"
43
45
  require_relative "ibanity/api/consent/consent"
44
46
  require_relative "ibanity/api/consent/processing_operation"
45
47
  require_relative "ibanity/api/ponto_connect/payment"
48
+ require_relative "ibanity/api/ponto_connect/user_info"
49
+ require_relative "ibanity/api/ponto_connect/usage"
50
+ require_relative "ibanity/api/ponto_connect/sandbox/financial_institution_account"
51
+ require_relative "ibanity/api/ponto_connect/sandbox/financial_institution_transaction"
46
52
 
47
53
  module Ibanity
48
54
  class << self
@@ -92,7 +92,7 @@ module Ibanity
92
92
  attributes = raw["attributes"] || {}
93
93
  meta = raw["meta"] || {}
94
94
  params = base.merge(attributes).merge(meta)
95
- Ibanity::Util.underscorize_hash(params)
95
+ Ibanity::Util.underscorize(params)
96
96
  end
97
97
 
98
98
  def setup_relationships(relationships, customer_access_token = nil)
@@ -6,6 +6,12 @@ module Ibanity
6
6
  new(raw_item)
7
7
  end
8
8
 
9
+ def self.find_by_uri(uri:, customer_access_token: nil, headers: nil)
10
+ raw_item = Ibanity.client.get(uri: uri, customer_access_token: customer_access_token, headers: headers)
11
+ raw_item = {} if raw_item == ""
12
+ new(raw_item)
13
+ end
14
+
9
15
  def initialize(raw)
10
16
  super(raw)
11
17
  end
@@ -0,0 +1,21 @@
1
+ module Ibanity
2
+ module PontoConnect
3
+ module Sandbox
4
+ class FinancialInstitutionAccount < Ibanity::BaseResource
5
+ def self.list(access_token:, financial_institution_id:, **query_params)
6
+ uri = Ibanity.ponto_connect_api_schema["sandbox"]["financialInstitution"]["financialInstitutionAccounts"]
7
+ .gsub("{financialInstitutionId}", financial_institution_id)
8
+ .gsub("{financialInstitutionAccountId}", "")
9
+ list_by_uri(uri: uri, query_params: query_params, customer_access_token: access_token)
10
+ end
11
+
12
+ def self.find(access_token:, id:, financial_institution_id:)
13
+ uri = Ibanity.ponto_connect_api_schema["sandbox"]["financialInstitution"]["financialInstitutionAccounts"]
14
+ .gsub("{financialInstitutionId}", financial_institution_id)
15
+ .gsub("{financialInstitutionAccountId}", id)
16
+ find_by_uri(uri: uri, customer_access_token: access_token)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ module Ibanity
2
+ module PontoConnect
3
+ module Sandbox
4
+ class FinancialInstitutionTransaction < Ibanity::BaseResource
5
+ def self.list(access_token:, financial_institution_account_id:, financial_institution_id:, **query_params)
6
+ uri = Ibanity.ponto_connect_api_schema["sandbox"]["financialInstitution"]["financialInstitutionAccount"]["financialInstitutionTransactions"]
7
+ .gsub("{financialInstitutionId}", financial_institution_id)
8
+ .gsub("{financialInstitutionAccountId}", financial_institution_account_id)
9
+ .gsub("{financialInstitutionTransactionId}", "")
10
+ list_by_uri(uri: uri, query_params: query_params, customer_access_token: access_token)
11
+ end
12
+
13
+ def self.find(access_token:, id:, financial_institution_id:, financial_institution_account_id:)
14
+ uri = Ibanity.ponto_connect_api_schema["sandbox"]["financialInstitution"]["financialInstitutionAccount"]["financialInstitutionTransactions"]
15
+ .gsub("{financialInstitutionId}", financial_institution_id)
16
+ .gsub("{financialInstitutionAccountId}", financial_institution_account_id)
17
+ .gsub("{financialInstitutionTransactionId}", id)
18
+ find_by_uri(uri: uri, customer_access_token: access_token)
19
+ end
20
+
21
+ def self.create(access_token:, financial_institution_id:, financial_institution_account_id:, **attributes)
22
+ uri = Ibanity.ponto_connect_api_schema["sandbox"]["financialInstitution"]["financialInstitutionAccount"]["financialInstitutionTransactions"]
23
+ .sub("{financialInstitutionId}", financial_institution_id)
24
+ .sub("{financialInstitutionAccountId}", financial_institution_account_id)
25
+ .sub("{financialInstitutionTransactionId}", "")
26
+ create_by_uri(uri: uri, resource_type: "financialInstitutionTransaction", attributes: attributes, customer_access_token: access_token)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,16 +2,33 @@ module Ibanity
2
2
  module PontoConnect
3
3
  class Token < Ibanity::OAuthResource
4
4
  def self.create(refresh_token: nil, authorization_code: nil, redirect_uri: nil, idempotency_key: nil, code_verifier: nil)
5
- uri = Ibanity.ponto_connect_api_schema["oauth2"]["token"]
6
- grant_type = refresh_token ? "refresh_token" : "authorization_code"
7
- arguments = [
8
- ["grant_type", grant_type],
9
- ["client_id", Ibanity.client.ponto_connect_client_id]
10
- ]
11
- arguments << ["code", authorization_code] << ["code_verifier", code_verifier] << ["redirect_uri", redirect_uri] if authorization_code
12
- arguments << ["refresh_token", refresh_token] if refresh_token
13
- payload = URI.encode_www_form(arguments)
14
- create_by_uri(uri: uri, payload: payload, idempotency_key: idempotency_key, headers: self.headers)
5
+ arguments =
6
+ if refresh_token
7
+ [
8
+ ["grant_type", "refresh_token"],
9
+ ["client_id", Ibanity.client.ponto_connect_client_id],
10
+ ["refresh_token", refresh_token]
11
+ ]
12
+ elsif authorization_code
13
+ [
14
+ ["grant_type", "authorization_code"],
15
+ ["client_id", Ibanity.client.ponto_connect_client_id],
16
+ ["code", authorization_code],
17
+ ["code_verifier", code_verifier],
18
+ ["redirect_uri", redirect_uri]
19
+ ]
20
+ else
21
+ [
22
+ ["grant_type", "client_credentials"]
23
+ ]
24
+ end
25
+
26
+ create_by_uri(
27
+ uri: Ibanity.ponto_connect_api_schema["oauth2"]["token"],
28
+ payload: URI.encode_www_form(arguments),
29
+ idempotency_key: idempotency_key,
30
+ headers: self.headers
31
+ )
15
32
  end
16
33
 
17
34
  def self.delete(refresh_token:)
@@ -0,0 +1,10 @@
1
+ module Ibanity
2
+ module PontoConnect
3
+ class Usage < Ibanity::BaseResource
4
+ def self.find(client_access_token:, organization_id:, month:)
5
+ uri = Ibanity.ponto_connect_api_schema["organizations"]["usage"].sub("{organizationId}", organization_id).sub("{month}", month)
6
+ find_by_uri(uri: uri, customer_access_token: client_access_token)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Ibanity
2
+ module PontoConnect
3
+ class UserInfo < Ibanity::OAuthResource
4
+ def self.find(access_token:)
5
+ uri = Ibanity.ponto_connect_api_schema["userinfo"]
6
+ find_by_uri(uri: uri, customer_access_token: access_token)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ module Ibanity
2
+ module Xs2a
3
+ class BulkPaymentInitiationRequest < Ibanity::BaseResource
4
+ def self.create_for_financial_institution(financial_institution_id:, customer_access_token:, idempotency_key: nil, **attributes)
5
+ path = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["bulkPaymentInitiationRequests"]
6
+ .gsub("{financialInstitutionId}", financial_institution_id)
7
+ .sub("{paymentInitiationRequestId}", "")
8
+ uri = Ibanity.client.build_uri(path)
9
+ create_by_uri(
10
+ uri: uri,
11
+ resource_type: "bulkPaymentInitiationRequest",
12
+ attributes: attributes,
13
+ customer_access_token: customer_access_token,
14
+ idempotency_key: idempotency_key
15
+ )
16
+ end
17
+
18
+ def self.find(id:, financial_institution_id:, customer_access_token:)
19
+ uri = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["bulkPaymentInitiationRequests"]
20
+ .gsub("{financialInstitutionId}", financial_institution_id)
21
+ .sub("{paymentInitiationRequestId}", id)
22
+ find_by_uri(uri: uri, customer_access_token: customer_access_token)
23
+ end
24
+
25
+ def self.delete(id:, financial_institution_id:, customer_access_token:)
26
+ uri = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["bulkPaymentInitiationRequests"]
27
+ .gsub("{financialInstitutionId}", financial_institution_id)
28
+ .sub("{paymentInitiationRequestId}", id)
29
+ destroy_by_uri(uri: uri, customer_access_token: customer_access_token)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Ibanity
2
+ module Xs2a
3
+ class PeriodicPaymentInitiationRequest < Ibanity::BaseResource
4
+ def self.create_for_financial_institution(financial_institution_id:, customer_access_token:, idempotency_key: nil, **attributes)
5
+ path = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["periodicPaymentInitiationRequests"]
6
+ .gsub("{financialInstitutionId}", financial_institution_id)
7
+ .sub("{paymentInitiationRequestId}", "")
8
+ uri = Ibanity.client.build_uri(path)
9
+ create_by_uri(
10
+ uri: uri,
11
+ resource_type: "periodicPaymentInitiationRequest",
12
+ attributes: attributes,
13
+ customer_access_token: customer_access_token,
14
+ idempotency_key: idempotency_key
15
+ )
16
+ end
17
+
18
+ def self.find(id:, financial_institution_id:, customer_access_token:)
19
+ uri = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["periodicPaymentInitiationRequests"]
20
+ .gsub("{financialInstitutionId}", financial_institution_id)
21
+ .sub("{paymentInitiationRequestId}", id)
22
+ find_by_uri(uri: uri, customer_access_token: customer_access_token)
23
+ end
24
+
25
+ def self.delete(id:, financial_institution_id:, customer_access_token:)
26
+ uri = Ibanity.xs2a_api_schema["customer"]["financialInstitution"]["periodicPaymentInitiationRequests"]
27
+ .gsub("{financialInstitutionId}", financial_institution_id)
28
+ .sub("{paymentInitiationRequestId}", id)
29
+ destroy_by_uri(uri: uri, customer_access_token: customer_access_token)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,6 +2,9 @@ require "base64"
2
2
 
3
3
  module Ibanity
4
4
  class HttpSignature
5
+ PSS_DIGEST_ALGORITHM = "SHA256"
6
+ SIGNATURE_ALGORITHM = "hs2019"
7
+
5
8
  def initialize(certificate:, certificate_id:, key:, method:, uri:, query_params:, headers:, payload:)
6
9
  @certificate = certificate
7
10
  @certificate_id = certificate_id
@@ -13,6 +16,21 @@ module Ibanity
13
16
  @query_params = query_params
14
17
  end
15
18
 
19
+ def signature_headers
20
+ {
21
+ "Digest" => payload_digest,
22
+ "Signature" => [
23
+ %(keyId="#{@certificate_id}"),
24
+ %(created="#{date}"),
25
+ %(algorithm="#{SIGNATURE_ALGORITHM}"),
26
+ %(headers="#{headers_to_sign.join(" ")}"),
27
+ %(signature="#{base64_signature}"),
28
+ ].join(",")
29
+ }
30
+ end
31
+
32
+ private
33
+
16
34
  def payload_digest
17
35
  digest = OpenSSL::Digest::SHA512.new
18
36
  string_payload = @payload.nil? ? "" : @payload
@@ -21,12 +39,8 @@ module Ibanity
21
39
  "SHA-512=#{base64}"
22
40
  end
23
41
 
24
- def signature_algorithm
25
- @certificate.signature_algorithm.match("sha256") ? "rsa-sha256" : "rsa-sha512"
26
- end
27
-
28
42
  def headers_to_sign
29
- result = ["(request-target)", "host", "digest", "date"]
43
+ result = ["(request-target)", "host", "digest", "(created)"]
30
44
  result << "authorization" unless @headers["Authorization"].nil?
31
45
  @headers.keys.each do |header|
32
46
  result << header.to_s.downcase if header.to_s.match(/ibanity/i)
@@ -35,13 +49,12 @@ module Ibanity
35
49
  end
36
50
 
37
51
  def request_target
38
- @uri.query = URI.encode_www_form(URI.decode_www_form(@uri.query.to_s).concat(@query_params.to_a)) if @query_params&.keys&.any?
52
+ @uri.query = RestClient::Utils.encode_query_string(@query_params) if @query_params&.keys&.any?
39
53
  "#{@method} #{@uri.request_uri}"
40
54
  end
41
55
 
42
56
  def base64_signature
43
- digest = signature_algorithm == "rsa-sha256" ? OpenSSL::Digest::SHA256.new : OpenSSL::Digest::SHA512.new
44
- signature = @key.sign(digest, signing_string)
57
+ signature = @key.sign_pss(PSS_DIGEST_ALGORITHM, signing_string, salt_length: :digest, mgf1_hash: PSS_DIGEST_ALGORITHM)
45
58
  Base64.urlsafe_encode64(signature)
46
59
  end
47
60
 
@@ -50,13 +63,13 @@ module Ibanity
50
63
  end
51
64
 
52
65
  def date
53
- @date ||= Time.now.utc.iso8601
66
+ @date ||= Time.now.to_i
54
67
  end
55
68
 
56
69
  def signing_string
57
70
  result = []
58
71
  headers_to_sign.each do |header_to_sign|
59
- value = header_value(header_to_sign)
72
+ value = header_value(header_to_sign)
60
73
  result << "#{header_to_sign}: #{value}"
61
74
  end
62
75
  result.join("\n")
@@ -70,20 +83,12 @@ module Ibanity
70
83
  host
71
84
  when "digest"
72
85
  payload_digest
73
- when "date"
86
+ when "(created)"
74
87
  date
75
88
  else
76
89
  camelized_header = header.split("-").collect(&:capitalize).join("-")
77
90
  @headers[camelized_header]
78
91
  end
79
92
  end
80
-
81
- def signature_headers
82
- {
83
- "Date" => date,
84
- "Digest" => payload_digest,
85
- "Signature" => "keyId=\"#{@certificate_id}\" algorithm=\"#{signature_algorithm}\" headers=\"#{headers_to_sign.join(" ")}\" signature=\"#{base64_signature}\""
86
- }
87
- end
88
93
  end
89
94
  end
@@ -1,9 +1,16 @@
1
1
  module Ibanity
2
2
  module Util
3
- def self.underscorize_hash(attributes)
4
- attributes.keys.reduce({}) do |result, key|
5
- result[underscore(key)] = attributes[key]
6
- result
3
+ def self.underscorize(obj)
4
+ case obj
5
+ when Array
6
+ obj.map { |e| underscorize(e) }
7
+ when Hash
8
+ obj.keys.reduce({}) do |result, key|
9
+ result[underscore(key)] = underscorize(obj[key])
10
+ result
11
+ end
12
+ else
13
+ obj
7
14
  end
8
15
  end
9
16
 
@@ -1,3 +1,3 @@
1
1
  module Ibanity
2
- VERSION = "1.0.1"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -0,0 +1,39 @@
1
+ require "ibanity/http_signature"
2
+
3
+ RSpec.describe Ibanity::HttpSignature do
4
+
5
+ describe ".signature_headers" do
6
+ let(:signature) do
7
+ Ibanity::HttpSignature.new(
8
+ certificate: CryptoHelper.load_certificate("test"),
9
+ certificate_id: "ec0c29ef-3b39-4b6f-93ff-866bed032399",
10
+ key: CryptoHelper.load_private_key("test"),
11
+ method: "post",
12
+ uri: "https://api.ibanity.com/xs2a/customer-access-tokens",
13
+ headers: {"ibanity-idempotency-key" => "cf17b515-5f6f-4213-a8fe-e4cd40653d00"},
14
+ payload: "{\"foo\": \"bar\"}",
15
+ query_params: {}
16
+ )
17
+ end
18
+
19
+ let(:parts_regex) do
20
+ /keyId="(?<keyId>.*)",\s?created="(?<created>.*)?",?\s?algorithm="(?<algorithm>.*)",\s?headers="(?<headers>.*)",?\s?signature="(?<signature>.*)"/
21
+ end
22
+
23
+ let(:signature_parts) do
24
+ signature.signature_headers["Signature"].match(parts_regex).named_captures
25
+ end
26
+
27
+ ["Digest", "Signature"].each do |header|
28
+ it "contains the '#{header}' header" do
29
+ expect(signature.signature_headers).to include(header)
30
+ end
31
+ end
32
+
33
+ ["keyId", "algorithm", "headers", "signature", "created"].each do |part|
34
+ it "has a signature containing the part '#{part}'" do
35
+ expect(signature_parts).to include(part)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ require 'ibanity/util'
2
+
3
+ RSpec.describe Ibanity::Util do
4
+ describe ".underscorize" do
5
+ it "transforms all keys to snake_case" do
6
+ hash = {
7
+ "currency" => "EUR",
8
+ "amount" => 1,
9
+ "debtorName" => "Sophie Schowalter",
10
+ "debtorAccountReference" => "BE59823362319793",
11
+ }
12
+
13
+ expected = {
14
+ "currency" => "EUR",
15
+ "amount" => 1,
16
+ "debtor_name" => "Sophie Schowalter",
17
+ "debtor_account_reference" => "BE59823362319793",
18
+ }
19
+
20
+ expect(Ibanity::Util.underscorize(hash)).to eq(expected)
21
+ end
22
+
23
+ it "processes hashes recursively" do
24
+ hash = {
25
+ "currency" => "EUR",
26
+ "amount" => 1,
27
+ "debtorName" => "Sophie Schowalter",
28
+ "details" => {
29
+ "debtorAccountReference" => "BE59823362319793"
30
+ }
31
+ }
32
+
33
+ expected = {
34
+ "currency" => "EUR",
35
+ "amount" => 1,
36
+ "debtor_name" => "Sophie Schowalter",
37
+ "details" => {
38
+ "debtor_account_reference" => "BE59823362319793"
39
+ }
40
+ }
41
+
42
+ expect(Ibanity::Util.underscorize(hash)).to eq(expected)
43
+ end
44
+
45
+ it "processes each array element" do
46
+ hash = {
47
+ "currency" => "EUR",
48
+ "amount" => 1,
49
+ "debtorName" => "Sophie Schowalter",
50
+ "details" => [
51
+ {"debtorAccountReference" => "BE59823362319793"}
52
+ ]
53
+ }
54
+
55
+ expected = {
56
+ "currency" => "EUR",
57
+ "amount" => 1,
58
+ "debtor_name" => "Sophie Schowalter",
59
+ "details" => [
60
+ {"debtor_account_reference" => "BE59823362319793"}
61
+ ]
62
+ }
63
+
64
+ expect(Ibanity::Util.underscorize(hash)).to eq(expected)
65
+ end
66
+
67
+ it "leaves arrays elements untouched if they are not themselves hashes or arrays" do
68
+ hash = {
69
+ "currency" => "EUR",
70
+ "amount" => 1,
71
+ "debtorName" => "Sophie Schowalter",
72
+ "details" => [
73
+ "BE59823362319793"
74
+ ]
75
+ }
76
+
77
+ expected = {
78
+ "currency" => "EUR",
79
+ "amount" => 1,
80
+ "debtor_name" => "Sophie Schowalter",
81
+ "details" => [
82
+ "BE59823362319793"
83
+ ]
84
+ }
85
+
86
+ expect(Ibanity::Util.underscorize(hash)).to eq(expected)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,102 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
17
+
18
+ RSpec.configure do |config|
19
+ # rspec-expectations config goes here. You can use an alternate
20
+ # assertion/expectation library such as wrong or the stdlib/minitest
21
+ # assertions if you prefer.
22
+ config.expect_with :rspec do |expectations|
23
+ # This option will default to `true` in RSpec 4. It makes the `description`
24
+ # and `failure_message` of custom matchers include text for helper methods
25
+ # defined using `chain`, e.g.:
26
+ # be_bigger_than(2).and_smaller_than(4).description
27
+ # # => "be bigger than 2 and smaller than 4"
28
+ # ...rather than:
29
+ # # => "be bigger than 2"
30
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
31
+ end
32
+
33
+ # rspec-mocks config goes here. You can use an alternate test double
34
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
35
+ config.mock_with :rspec do |mocks|
36
+ # Prevents you from mocking or stubbing a method that does not exist on
37
+ # a real object. This is generally recommended, and will default to
38
+ # `true` in RSpec 4.
39
+ mocks.verify_partial_doubles = true
40
+ end
41
+
42
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
43
+ # have no way to turn it off -- the option exists only for backwards
44
+ # compatibility in RSpec 3). It causes shared context metadata to be
45
+ # inherited by the metadata hash of host groups and examples, rather than
46
+ # triggering implicit auto-inclusion in groups with matching metadata.
47
+ config.shared_context_metadata_behavior = :apply_to_host_groups
48
+
49
+ # The settings below are suggested to provide a good initial experience
50
+ # with RSpec, but feel free to customize to your heart's content.
51
+ =begin
52
+ # This allows you to limit a spec run to individual examples or groups
53
+ # you care about by tagging them with `:focus` metadata. When nothing
54
+ # is tagged with `:focus`, all examples get run. RSpec also provides
55
+ # aliases for `it`, `describe`, and `context` that include `:focus`
56
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
57
+ config.filter_run_when_matching :focus
58
+
59
+ # Allows RSpec to persist some state between runs in order to support
60
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
61
+ # you configure your source control system to ignore this file.
62
+ config.example_status_persistence_file_path = "spec/examples.txt"
63
+
64
+ # Limits the available syntax to the non-monkey patched syntax that is
65
+ # recommended. For more details, see:
66
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
67
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
69
+ config.disable_monkey_patching!
70
+
71
+ # This setting enables warnings. It's recommended, but in some cases may
72
+ # be too noisy due to issues in dependencies.
73
+ config.warnings = true
74
+
75
+ # Many RSpec users commonly either run the entire suite or an individual
76
+ # file, and it's useful to allow more verbose output when running an
77
+ # individual spec file.
78
+ if config.files_to_run.one?
79
+ # Use the documentation formatter for detailed output,
80
+ # unless a formatter has already been configured
81
+ # (e.g. via a command-line flag).
82
+ config.default_formatter = "doc"
83
+ end
84
+
85
+ # Print the 10 slowest examples and example groups at the
86
+ # end of the spec run, to help surface which specs are running
87
+ # particularly slow.
88
+ config.profile_examples = 10
89
+
90
+ # Run specs in random order to surface order dependencies. If you find an
91
+ # order dependency and want to debug it, you can fix the order by providing
92
+ # the seed, which is printed after each run.
93
+ # --seed 1234
94
+ config.order = :random
95
+
96
+ # Seed global randomization in this process using the `--seed` CLI option.
97
+ # Setting this allows you to use `--seed` to deterministically reproduce
98
+ # test failures related to randomization by passing the same `--seed` value
99
+ # as the one that triggered the failure.
100
+ Kernel.srand config.seed
101
+ =end
102
+ end
@@ -0,0 +1,21 @@
1
+ require "pathname"
2
+ require "openssl"
3
+
4
+ module CryptoHelper
5
+ FIXTURE_DIRECTORY = Pathname.getwd().join("spec", "support", "fixtures", "signature")
6
+
7
+ def self.load_certificate(name)
8
+ pem = File.read(FIXTURE_DIRECTORY.join("#{name}-certificate.pem"))
9
+ ::OpenSSL::X509::Certificate.new(pem)
10
+ end
11
+
12
+ def self.load_public_key(name)
13
+ pem = File.read(FIXTURE_DIRECTORY.join("#{name}-public_key.pem"))
14
+ ::OpenSSL::PKey::RSA.new(pem)
15
+ end
16
+
17
+ def self.load_private_key(name)
18
+ pem = File.read(FIXTURE_DIRECTORY.join("#{name}-private_key.pem"))
19
+ ::OpenSSL::PKey::RSA.new(pem)
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDdjCCAl4CCQCxpvuEfl5slTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJC
3
+ RTERMA8GA1UECAwIQnJ1c3NlbHMxETAPBgNVBAcMCEJydXNzZWxzMRAwDgYDVQQK
4
+ DAdJYmFuaXR5MRMwEQYDVQQDDAppYmFuaXR5LmJlMSEwHwYJKoZIhvcNAQkBFhJj
5
+ b21wYW55QGliYW5pdHkuYmUwHhcNMTkwNjI3MTE1NjAyWhcNMjkwNjI0MTE1NjAy
6
+ WjB9MQswCQYDVQQGEwJCRTERMA8GA1UECAwIQnJ1c3NlbHMxETAPBgNVBAcMCEJy
7
+ dXNzZWxzMRAwDgYDVQQKDAdJYmFuaXR5MRMwEQYDVQQDDAppYmFuaXR5LmJlMSEw
8
+ HwYJKoZIhvcNAQkBFhJjb21wYW55QGliYW5pdHkuYmUwggEiMA0GCSqGSIb3DQEB
9
+ AQUAA4IBDwAwggEKAoIBAQDcaDiqP4EkTYXzDqCLZGiEEQnx0iazY/f3IMZNnYVf
10
+ BDSLl3m2x1+3ch4Pe/E35Aag1webnvDh/VVRch0+n4KV/4Toe0Oq9VP6xKqOXYtZ
11
+ ZAVWXzs1TjdT/dk+rRXa+wnyq2KklPJ0aowzAyr3EzivW1v19uhWdqguhPEPopr/
12
+ 2sue3vloZrigamOov29iFkiFLtSqAyEZI7x48w/3X2nkZ1UivYxBRrLnulVMUJDZ
13
+ zvydu3stlu6mSlaQ3Mpdgzyz4V41NImzNeJnW1F6riwd6hud9xokDZSvredC/XhM
14
+ byl0LdrKABzyHnOFSBlIkIHDeQUPa9YKBESRMtOVmyrvAgMBAAEwDQYJKoZIhvcN
15
+ AQELBQADggEBAKMwFzfoXXxasB/6ZlnAMy7aWmb8xp55E6aZZoZXhoHHA03z+Jj2
16
+ cVy/PMgNDOk5MLf4ddpj7iVf42iLAGpgqo0oQjE11UCwYBBDIQ3vjR+MctoN2B+L
17
+ Qpx7OGDoqSnVucVTS9KmWbJM3PAfbeexSRfqRJ3Jn2DvQDx1k32L//KtP1FRMtku
18
+ Dqoizz7NSLjrmIkXLAuepKdfDSVKnLHD1fB6gKvDNPBNivlFqceZe0XoXdlGwZcC
19
+ 7aTdzYzZNCbg1w1bTxv7dom3ubAM1DPKUEA3afW25691ZsBf9W52hb/tOlVOw78j
20
+ +gUJtiEKTkcfmrfCgPDrcDjsm+iaQclAhK4=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcaDiqP4EkTYXz
3
+ DqCLZGiEEQnx0iazY/f3IMZNnYVfBDSLl3m2x1+3ch4Pe/E35Aag1webnvDh/VVR
4
+ ch0+n4KV/4Toe0Oq9VP6xKqOXYtZZAVWXzs1TjdT/dk+rRXa+wnyq2KklPJ0aowz
5
+ Ayr3EzivW1v19uhWdqguhPEPopr/2sue3vloZrigamOov29iFkiFLtSqAyEZI7x4
6
+ 8w/3X2nkZ1UivYxBRrLnulVMUJDZzvydu3stlu6mSlaQ3Mpdgzyz4V41NImzNeJn
7
+ W1F6riwd6hud9xokDZSvredC/XhMbyl0LdrKABzyHnOFSBlIkIHDeQUPa9YKBESR
8
+ MtOVmyrvAgMBAAECggEAOS3olXJIJIzgFUBUMhVob+qjs9KbK6rhp4EfMP+OnCnR
9
+ H+26K8rpcAPw/H9hAujrN0rRtHO1dktsmOaL47UqAZP6fP2NfoqKsOHYhXqLLjOe
10
+ ltu51ohmHioa9AGfS+IYoJYJzzy88aq6mHlX6iVYbVW8M8FMYTIDS549k5rRr1Iu
11
+ SqGQSoIo8vGKhMyp6UbIvBhx1CRs2xeoknL0H5uEHnqB6oJTqfnbkQ1sVX4vZuHl
12
+ +WOIKQPlFKy74Lcs9ap1Dr9llD/PoVB3b2KbLRJTda352ezMLspb6CVUOFw7S0eB
13
+ vHVFlR+V922GaMvEmxZ58ZY+DwqBGPeuiG7/kYFNAQKBgQD8e64NptLgw7Wk8mvn
14
+ tGTB28ycUkc9wT/NdiePqlUT56ynD4oCBGcbEu6hH5ntYR93Kpi7fINTKglkVGdK
15
+ MkU/QOV0bs7wItqmQvkMeUbypnWXeQPICs3z/synytwbDYl7/FslTPxdjAgodQZJ
16
+ FY3Lui9bqe05VjvefEtEb9/yrwKBgQDfeimt/bVmA6Ldg4k+K72aAwP+g8l8Dyg1
17
+ Fba2vSFoS/MyvhCqTgP6Uh2fl8qXOqGJEv0koD3yRADa5/FlHSCeLhsyYqsLd/Zm
18
+ 0Ogts7dhVm2v9jvVdmnWUQnGp/mPAGX6gB6Q1WHHEAdUNp6qAG7GatrJcttU0+7U
19
+ 9cYKyc9bwQKBgC7oi53dsLAxrD3JDGMwEMgzngAtCS9gCAqUOSVn8AaStHEVYf6d
20
+ 8soE6nDk/iQsNzxcnaO3rm51EOmjBM20KUlnNTo8nBXhY94f80VuAtByPMa3pQw1
21
+ da4vWLaT6fDcwv5WFFkJxJlcuudJVrGdX4rKPKI7H+fXLahYT2OXpXPhAoGBAIO9
22
+ Vli1WXw75ITFB+DYlDr4UCB3vA0gOkmg9Ucgk1MSgtmE5fofZ4TnZ4MvTkR8UcGm
23
+ qggvVpU8tWxWkx1SYGofL/Ux6Tcnjt/pgxV9/jqpYpv6gidCWP34Y9TyLNG6IGPd
24
+ pycmQy/AKTHhyQLaonLhhvx+cwG8texgvlCZy12BAoGAYsahYudgvuUi/IKjQvkH
25
+ 0F/kwBnd0mRHttsEMIVjrG1Y82EUh5Ja05IGpOSSMeBVhIlJ/DnwFOoSmIVzGfsA
26
+ 4GHtXmHd1xByxjW4nbr4NH7u8ElULkOPjcxSLgXhDb5KtG1XwCPEMUqvApcM6cfv
27
+ CQn8vQRR2J42gXEcTmt0zVQ=
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Gg4qj+BJE2F8w6gi2Ro
3
+ hBEJ8dIms2P39yDGTZ2FXwQ0i5d5tsdft3IeD3vxN+QGoNcHm57w4f1VUXIdPp+C
4
+ lf+E6HtDqvVT+sSqjl2LWWQFVl87NU43U/3ZPq0V2vsJ8qtipJTydGqMMwMq9xM4
5
+ r1tb9fboVnaoLoTxD6Ka/9rLnt75aGa4oGpjqL9vYhZIhS7UqgMhGSO8ePMP919p
6
+ 5GdVIr2MQUay57pVTFCQ2c78nbt7LZbupkpWkNzKXYM8s+FeNTSJszXiZ1tReq4s
7
+ HeobnfcaJA2Ur63nQv14TG8pdC3aygAc8h5zhUgZSJCBw3kFD2vWCgREkTLTlZsq
8
+ 7wIDAQAB
9
+ -----END PUBLIC KEY-----
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ibanity
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ibanity
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-30 00:00:00.000000000 Z
11
+ date: 2020-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -30,28 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 3.4.0
33
+ version: 3.9.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 3.4.0
41
- - !ruby/object:Gem::Dependency
42
- name: webmock
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '='
46
- - !ruby/object:Gem::Version
47
- version: 1.24.2
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '='
53
- - !ruby/object:Gem::Version
54
- version: 1.24.2
40
+ version: 3.9.0
55
41
  description: A Ruby wrapper for the Ibanity API.
56
42
  email:
57
43
  - info@ibanity.com
@@ -61,6 +47,7 @@ extra_rdoc_files: []
61
47
  files:
62
48
  - ".gitignore"
63
49
  - ".gitkeep"
50
+ - ".rspec"
64
51
  - ".travis.yml"
65
52
  - CHANGELOG.md
66
53
  - Gemfile
@@ -84,9 +71,13 @@ files:
84
71
  - lib/ibanity/api/ponto_connect/account.rb
85
72
  - lib/ibanity/api/ponto_connect/financial_institution.rb
86
73
  - lib/ibanity/api/ponto_connect/payment.rb
74
+ - lib/ibanity/api/ponto_connect/sandbox/financial_institution_account.rb
75
+ - lib/ibanity/api/ponto_connect/sandbox/financial_institution_transaction.rb
87
76
  - lib/ibanity/api/ponto_connect/synchronization.rb
88
77
  - lib/ibanity/api/ponto_connect/token.rb
89
78
  - lib/ibanity/api/ponto_connect/transaction.rb
79
+ - lib/ibanity/api/ponto_connect/usage.rb
80
+ - lib/ibanity/api/ponto_connect/user_info.rb
90
81
  - lib/ibanity/api/sandbox/financial_institution_account.rb
91
82
  - lib/ibanity/api/sandbox/financial_institution_holding.rb
92
83
  - lib/ibanity/api/sandbox/financial_institution_transaction.rb
@@ -94,12 +85,14 @@ files:
94
85
  - lib/ibanity/api/xs2a/account.rb
95
86
  - lib/ibanity/api/xs2a/account_information_access_request.rb
96
87
  - lib/ibanity/api/xs2a/account_information_access_request_authorization.rb
88
+ - lib/ibanity/api/xs2a/bulk_payment_initiation_request.rb
97
89
  - lib/ibanity/api/xs2a/customer.rb
98
90
  - lib/ibanity/api/xs2a/customer_access_token.rb
99
91
  - lib/ibanity/api/xs2a/financial_institution.rb
100
92
  - lib/ibanity/api/xs2a/holding.rb
101
93
  - lib/ibanity/api/xs2a/payment_initiation_request.rb
102
94
  - lib/ibanity/api/xs2a/payment_initiation_request_authorization.rb
95
+ - lib/ibanity/api/xs2a/periodic_payment_initiation_request.rb
103
96
  - lib/ibanity/api/xs2a/synchronization.rb
104
97
  - lib/ibanity/api/xs2a/transaction.rb
105
98
  - lib/ibanity/client.rb
@@ -108,6 +101,13 @@ files:
108
101
  - lib/ibanity/http_signature.rb
109
102
  - lib/ibanity/util.rb
110
103
  - lib/ibanity/version.rb
104
+ - spec/lib/ibanity/http_signature_spec.rb
105
+ - spec/lib/ibanity/util_spec.rb
106
+ - spec/spec_helper.rb
107
+ - spec/support/crypto_helper.rb
108
+ - spec/support/fixtures/signature/test-certificate.pem
109
+ - spec/support/fixtures/signature/test-private_key.pem
110
+ - spec/support/fixtures/signature/test-public_key.pem
111
111
  homepage: https://documentation.ibanity.com/api/ruby
112
112
  licenses:
113
113
  - MIT
@@ -127,9 +127,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  - !ruby/object:Gem::Version
128
128
  version: '0'
129
129
  requirements: []
130
- rubyforge_project:
131
- rubygems_version: 2.7.7
130
+ rubygems_version: 3.0.8
132
131
  signing_key:
133
132
  specification_version: 4
134
133
  summary: Ibanity Ruby Client
135
- test_files: []
134
+ test_files:
135
+ - spec/lib/ibanity/http_signature_spec.rb
136
+ - spec/lib/ibanity/util_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/support/crypto_helper.rb
139
+ - spec/support/fixtures/signature/test-certificate.pem
140
+ - spec/support/fixtures/signature/test-private_key.pem
141
+ - spec/support/fixtures/signature/test-public_key.pem