ibanity 1.9.1 → 1.10.0

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