ibanity 1.9.1 → 1.10.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: 69d8a3c866e929ec613b9b17c146269217889e0e498b7f5c9fe0a84c0db5b643
4
- data.tar.gz: 68ccaee907f5082fa520acbaee93a4591fe888575f479a1b3c71e6432191cd48
3
+ metadata.gz: 55b04259d7e3e1f8cd8ee53b23d5b50fd4803d0a15822870b59b2e3bd78820a2
4
+ data.tar.gz: 79003b1d051b9e517ea786dab240b19dca82bfcbf8379784eacccdd15a7e8481
5
5
  SHA512:
6
- metadata.gz: 8825787922d34faf6745621d921377e1e187d542eb26b487a6071075db654399d5f3f7d5110c2028bcb9720f29f28f219a73374fc019a217ba3eb812eaead62c
7
- data.tar.gz: a43e981bcc792e9541bd17ce01290921ca1ae9b9676221e234cc574214537dca60bccd15b66b349dd392eb8dd1d9f878109b27a708b0e7d6e2637d2685c06752
6
+ metadata.gz: f11e1268e728697e6f75cdc0eb7a79358d8359ee4eada54ddf7e452ef7147b9f0c548724047a5d99454ca785840ab5e72c0b16448b587789d27f3b6ced89a47e
7
+ data.tar.gz: 96132c499b4e33ed9681f6998d1f7923e260d715078777a503dfd13c98a4979fcf03854fa96f217d3527a0e248cec994710fdfe74b3816afc92302d91ab60316
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.10
4
+
5
+ * [Ponto Connect] Add support for payment activation requests
6
+
7
+ * [Webhooks] Add support for webhook signature validation, keys endpoint, and events.
8
+
3
9
  ## 1.9
4
10
 
5
11
  * [Ponto Connect] Add account reauthorization requests
data/ibanity.gemspec CHANGED
@@ -19,5 +19,6 @@ 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_dependency "jose", ">= 1.1.3"
22
23
  spec.add_development_dependency "rspec", "3.9.0"
23
24
  end
@@ -0,0 +1,20 @@
1
+ module Ibanity
2
+ class FlatResource < OpenStruct
3
+ def self.list_by_uri(uri:, key:)
4
+ raw_response = Ibanity.client.get(uri: uri)
5
+ items = raw_response[key].map { |raw_item| new(raw_item) }
6
+ Ibanity::Collection.new(
7
+ klass: self,
8
+ items: items,
9
+ links: nil,
10
+ paging: nil,
11
+ synchronized_at: nil,
12
+ latest_synchronization: nil
13
+ )
14
+ end
15
+
16
+ def initialize(raw)
17
+ super(raw)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module Ibanity
2
+ module PontoConnect
3
+ class PaymentActivationRequest < Ibanity::BaseResource
4
+ def self.create(access_token:, **attributes)
5
+ uri = Ibanity.ponto_connect_api_schema["paymentActivationRequests"]
6
+ create_by_uri(uri: uri, resource_type: "paymentActivationRequest", attributes: attributes, customer_access_token: access_token)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Ibanity
2
+ module Webhooks
3
+ class Key < Ibanity::FlatResource
4
+ def self.list
5
+ path = Ibanity.webhooks_api_schema["keys"]
6
+ uri = Ibanity.client.build_uri(path)
7
+ list_by_uri(uri: uri, key: "keys")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Ibanity
2
+ module Webhooks
3
+ module PontoConnect
4
+ module Synchronization
5
+ class SucceededWithoutChange < Ibanity::BaseResource
6
+ end
7
+
8
+ class Failed < Ibanity::BaseResource
9
+ end
10
+ end
11
+
12
+ module Account
13
+ class DetailsUpdated < Ibanity::BaseResource
14
+ end
15
+
16
+ class TransactionsCreated < Ibanity::BaseResource
17
+ end
18
+
19
+ class TransactionsUpdated < Ibanity::BaseResource
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Ibanity
2
+ module Webhooks
3
+ module Xs2a
4
+ module Synchronization
5
+ class SucceededWithoutChange < Ibanity::BaseResource
6
+ end
7
+
8
+ class Failed < Ibanity::BaseResource
9
+ end
10
+ end
11
+
12
+ module Account
13
+ class DetailsUpdated < Ibanity::BaseResource
14
+ end
15
+
16
+ class TransactionsCreated < Ibanity::BaseResource
17
+ end
18
+
19
+ class TransactionsUpdated < Ibanity::BaseResource
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,7 +1,7 @@
1
1
  module Ibanity
2
2
  class Client
3
3
 
4
- attr_reader :base_uri, :signature_certificate, :signature_key, :isabel_connect_client_id, :isabel_connect_client_secret, :ponto_connect_client_id, :ponto_connect_client_secret
4
+ attr_reader :base_uri, :signature_certificate, :signature_key, :isabel_connect_client_id, :isabel_connect_client_secret, :ponto_connect_client_id, :ponto_connect_client_secret, :application_id
5
5
 
6
6
  def initialize(
7
7
  certificate:,
@@ -13,12 +13,12 @@ module Ibanity
13
13
  signature_key_passphrase: nil,
14
14
  api_scheme: "https",
15
15
  api_host: "api.ibanity.com",
16
- api_port: "443",
17
16
  ssl_ca_file: nil,
18
17
  isabel_connect_client_id: "valid_client_id",
19
18
  isabel_connect_client_secret: "valid_client_secret",
20
19
  ponto_connect_client_id: nil,
21
20
  ponto_connect_client_secret: nil,
21
+ application_id: nil,
22
22
  debug_http_requests: false)
23
23
  @isabel_connect_client_id = isabel_connect_client_id
24
24
  @isabel_connect_client_secret = isabel_connect_client_secret
@@ -32,8 +32,9 @@ module Ibanity
32
32
  @signature_certificate_id = signature_certificate_id
33
33
  @signature_key = OpenSSL::PKey::RSA.new(signature_key, signature_key_passphrase)
34
34
  end
35
- @base_uri = "#{api_scheme}://#{api_host}:#{api_port}"
35
+ @base_uri = "#{api_scheme}://#{api_host}"
36
36
  @ssl_ca_file = ssl_ca_file
37
+ @application_id = application_id
37
38
  end
38
39
 
39
40
  def get(uri:, query_params: {}, customer_access_token: nil, headers: nil, json: true)
data/lib/ibanity/error.rb CHANGED
@@ -22,6 +22,8 @@ module Ibanity
22
22
  formatted_errors.join("\n")
23
23
  elsif @errors.is_a?(Hash)
24
24
  "* #{@errors["error"]}: #{@errors["error_description"]}\n * Error hint: #{@errors["error_hint"]}\n * ibanity_request_id: #{@ibanity_request_id}"
25
+ elsif @errors.is_a?(String)
26
+ @errors
25
27
  else
26
28
  super
27
29
  end
@@ -1,3 +1,3 @@
1
1
  module Ibanity
2
- VERSION = "1.9.1"
2
+ VERSION = "1.10.0"
3
3
  end
@@ -0,0 +1,94 @@
1
+ module Ibanity
2
+ module Webhook
3
+ DEFAULT_TOLERANCE = 30
4
+ SIGNING_ALGORITHM = "RS512"
5
+
6
+ # Initializes an Ibanity Webhooks event object from a JSON payload.
7
+ #
8
+ # This may raise JSON::ParserError if the payload is not valid JSON, or
9
+ # Ibanity::Error if the signature verification fails.
10
+ def self.construct_event!(payload, signature_header, tolerance: DEFAULT_TOLERANCE)
11
+ Signature.verify!(payload, signature_header, tolerance)
12
+
13
+ raw_item = JSON.parse(payload)
14
+ klass = raw_item["data"]["type"].split(".").map{|klass| klass.sub(/\S/, &:upcase)}.join("::")
15
+ Ibanity::Webhooks.const_get(klass).new(raw_item["data"])
16
+ end
17
+
18
+ module Signature
19
+ # Verifies the signature header for a given payload.
20
+ #
21
+ # Raises an Ibanity::Error in the following cases:
22
+ # - the header does not match the expected format
23
+ # - the digest does not match the payload
24
+ # - the issued at or expiration timestamp is not within the tolerance
25
+ # - the audience or issuer does not match the application config
26
+ #
27
+ # Returns true otherwise
28
+ def self.verify!(payload, signature_header, tolerance)
29
+ begin
30
+ key_details = JOSE::JWT.peek_protected(signature_header).to_hash
31
+ raise unless key_details["alg"] == SIGNING_ALGORITHM && key_details["kid"]
32
+ rescue
33
+ raise Ibanity::Error.new("Key details could not be parsed from the header", nil)
34
+ end
35
+
36
+ key = Ibanity.webhook_keys.find { |key| key.kid == key_details["kid"] }
37
+
38
+ if key.nil?
39
+ raise Ibanity::Error.new("The key id from the header didn't match an available signing key", nil)
40
+ end
41
+ signer = JOSE::JWK.from(key.to_h {|key, value| [key.to_s, value] })
42
+ verified, claims_string, _jws = JOSE::JWT.verify_strict(signer, [SIGNING_ALGORITHM], signature_header)
43
+
44
+ raise Ibanity::Error.new("The signature verification failed", nil) unless verified
45
+
46
+ jwt = JOSE::JWT.from(claims_string)
47
+
48
+ validate_digest!(jwt, payload)
49
+ validate_issued_at!(jwt, tolerance)
50
+ validate_expiration!(jwt, tolerance)
51
+ validate_issuer!(jwt)
52
+ validate_audience!(jwt)
53
+
54
+ true
55
+ end
56
+
57
+ private
58
+
59
+ def self.validate_digest!(jwt, payload)
60
+ unless Digest::SHA512.base64digest(payload) == jwt.fields["digest"]
61
+ raise_invalid_signature_error!("digest")
62
+ end
63
+ end
64
+
65
+ def self.validate_issued_at!(jwt, tolerance)
66
+ unless jwt.fields["iat"] <= Time.now.to_i + tolerance
67
+ raise_invalid_signature_error!("iat")
68
+ end
69
+ end
70
+
71
+ def self.validate_expiration!(jwt, tolerance)
72
+ unless jwt.fields["exp"] >= Time.now.to_i - tolerance
73
+ raise_invalid_signature_error!("exp")
74
+ end
75
+ end
76
+
77
+ def self.validate_issuer!(jwt)
78
+ unless jwt.fields["iss"] == Ibanity.client.base_uri
79
+ raise_invalid_signature_error!("iss")
80
+ end
81
+ end
82
+
83
+ def self.validate_audience!(jwt)
84
+ unless jwt.fields["aud"] == Ibanity.client.application_id
85
+ raise_invalid_signature_error!("aud")
86
+ end
87
+ end
88
+
89
+ def self.raise_invalid_signature_error!(field)
90
+ raise Ibanity::Error.new("The signature #{field} is invalid", nil)
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/ibanity.rb CHANGED
@@ -4,6 +4,7 @@ require "uri"
4
4
  require "rest_client"
5
5
  require "json"
6
6
  require "securerandom"
7
+ require "jose"
7
8
 
8
9
  require_relative "ibanity/util"
9
10
  require_relative "ibanity/error"
@@ -11,6 +12,8 @@ require_relative "ibanity/collection"
11
12
  require_relative "ibanity/client"
12
13
  require_relative "ibanity/http_signature"
13
14
  require_relative "ibanity/api/base_resource"
15
+ require_relative "ibanity/api/flat_resource"
16
+ require_relative "ibanity/webhook"
14
17
  require_relative "ibanity/api/xs2a/account"
15
18
  require_relative "ibanity/api/xs2a/transaction"
16
19
  require_relative "ibanity/api/xs2a/holding"
@@ -55,6 +58,10 @@ require_relative "ibanity/api/ponto_connect/sandbox/financial_institution_accoun
55
58
  require_relative "ibanity/api/ponto_connect/sandbox/financial_institution_transaction"
56
59
  require_relative "ibanity/api/ponto_connect/onboarding_details"
57
60
  require_relative "ibanity/api/ponto_connect/reauthorization_request"
61
+ require_relative "ibanity/api/ponto_connect/payment_activation_request"
62
+ require_relative "ibanity/api/webhooks/key"
63
+ require_relative "ibanity/api/webhooks/xs2a"
64
+ require_relative "ibanity/api/webhooks/ponto_connect"
58
65
 
59
66
  module Ibanity
60
67
  class << self
@@ -69,6 +76,7 @@ module Ibanity
69
76
  @isabel_connect_api_schema = nil
70
77
  @consent_api_schema = nil
71
78
  @ponto_connect_api_schema = nil
79
+ @webhooks_api_schema = nil
72
80
  @sandbox_api_schema = nil
73
81
  @configuration = nil
74
82
  yield configuration
@@ -89,8 +97,8 @@ module Ibanity
89
97
  :ponto_connect_client_secret,
90
98
  :api_scheme,
91
99
  :api_host,
92
- :api_port,
93
100
  :ssl_ca_file,
101
+ :application_id,
94
102
  :debug_http_requests
95
103
  ).new
96
104
  end
@@ -115,6 +123,14 @@ module Ibanity
115
123
  @consent_api_schema ||= client.get(uri: "#{client.base_uri}/consent")["links"]
116
124
  end
117
125
 
126
+ def webhooks_api_schema
127
+ @webhooks_api_schema ||= client.get(uri: "#{client.base_uri}/webhooks")["links"]
128
+ end
129
+
130
+ def webhook_keys
131
+ @webhook_keys ||= Ibanity::Webhooks::Key.list
132
+ end
133
+
118
134
  def respond_to_missing?(method_name, include_private = false)
119
135
  client.respond_to?(method_name, include_private)
120
136
  end
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.9.1
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ibanity
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-30 00:00:00.000000000 Z
11
+ date: 2022-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.8.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: jose
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.3
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -60,6 +74,7 @@ files:
60
74
  - lib/ibanity/api/base_resource.rb
61
75
  - lib/ibanity/api/consent/consent.rb
62
76
  - lib/ibanity/api/consent/processing_operation.rb
77
+ - lib/ibanity/api/flat_resource.rb
63
78
  - lib/ibanity/api/isabel_connect/access_token.rb
64
79
  - lib/ibanity/api/isabel_connect/account.rb
65
80
  - lib/ibanity/api/isabel_connect/account_report.rb
@@ -76,6 +91,7 @@ files:
76
91
  - lib/ibanity/api/ponto_connect/integration.rb
77
92
  - lib/ibanity/api/ponto_connect/onboarding_details.rb
78
93
  - lib/ibanity/api/ponto_connect/payment.rb
94
+ - lib/ibanity/api/ponto_connect/payment_activation_request.rb
79
95
  - lib/ibanity/api/ponto_connect/reauthorization_request.rb
80
96
  - lib/ibanity/api/ponto_connect/sandbox/financial_institution_account.rb
81
97
  - lib/ibanity/api/ponto_connect/sandbox/financial_institution_transaction.rb
@@ -88,6 +104,9 @@ files:
88
104
  - lib/ibanity/api/sandbox/financial_institution_holding.rb
89
105
  - lib/ibanity/api/sandbox/financial_institution_transaction.rb
90
106
  - lib/ibanity/api/sandbox/financial_institution_user.rb
107
+ - lib/ibanity/api/webhooks/key.rb
108
+ - lib/ibanity/api/webhooks/ponto_connect.rb
109
+ - lib/ibanity/api/webhooks/xs2a.rb
91
110
  - lib/ibanity/api/xs2a/account.rb
92
111
  - lib/ibanity/api/xs2a/account_information_access_request.rb
93
112
  - lib/ibanity/api/xs2a/account_information_access_request_authorization.rb
@@ -108,6 +127,7 @@ files:
108
127
  - lib/ibanity/http_signature.rb
109
128
  - lib/ibanity/util.rb
110
129
  - lib/ibanity/version.rb
130
+ - lib/ibanity/webhook.rb
111
131
  - spec/lib/ibanity/base_resource_spec.rb
112
132
  - spec/lib/ibanity/http_signature_spec.rb
113
133
  - spec/lib/ibanity/util_spec.rb