apimatic_core 0.3.18 → 0.3.20

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: 2b0a483f088067743a544f3de82e94a1fd7e69c8ae909183b78f4d1ef5b1ae58
4
- data.tar.gz: cd450513cff448d0a5c26ab010965d1a6c96c69aaa4d28867a4837b23860ddd0
3
+ metadata.gz: f7d8b0c1b444eea245cf2e0ca29d5fbd980529d682183b50a74e9a930fa90930
4
+ data.tar.gz: 72438e6b5d173296dac13e5289e93ca9705de8a28e57b9beff52e7c59f9f6607
5
5
  SHA512:
6
- metadata.gz: e979257d3120334ba5b91869185aabb76a370d9fb7e8dff380d11275be052dcee00162b46e72e426812f4228b0c93fb7eda06bdc12ecdfce931b2529d00475ec
7
- data.tar.gz: 2adbc4d4e20342ff76a26b90225a6cce77e0ff7ba2a6ee0d7e1904682369a561c513a5b6fe9668ed0fd801ec49d445d496f520ddc9b96e05934dabd1ee7d3d53
6
+ metadata.gz: 3bf3553f1053f3da1ab4a9e971aec4466d20ff84bcecdf2bf20051f3920e4ba0f471f782dc606875429214196ba934d9988ab9a2884a193a6f4aade5f692d1e4
7
+ data.tar.gz: e39757f877aeba7cd2f80cc70d15076daa9b44700dab1830608cc77b7799866e5abc389aa502b64aeeb2ad715a128ba4ddf898c853ffda4ffb2601f37425111a
data/README.md CHANGED
@@ -45,7 +45,7 @@ gem 'apimatic_core'
45
45
  | [`Single`](lib/apimatic-core/authentication/multiple/single_auth.rb) | Helper class to support single authentication |
46
46
 
47
47
 
48
- ## Configurations
48
+ ## Global Configuration
49
49
  | Name | Description |
50
50
  |------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
51
51
  | [`GlobalConfiguration`](lib/apimatic-core/configurations/global_configuration.rb ) | Class holding the global configuration properties to make a successful API Call |
@@ -63,14 +63,19 @@ gem 'apimatic_core'
63
63
  |-------------------------------------------------------------------------------|------------------------------------------|
64
64
  | [`HttpResponseFactory`](lib/apimatic-core/factories/http_response_factory.rb) | Factory class to create an HTTP Response |
65
65
 
66
+ ## HTTP Configuration
67
+ | Name | Description |
68
+ |-------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
69
+ | [`HttpClientConfiguration`](lib/apimatic-core/http/configurations/http_client_configuration.rb) | Class used for configuring SDK by a user |
70
+ | [`ProxySettings`](lib/apimatic-core/http/configurations/proxy_settings.rb) | ProxySettings encapsulates HTTP proxy configuration for Faraday, e.g. address, port and optional basic authentication |
71
+
66
72
  ## HTTP
67
- | Name | Description |
68
- |-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
69
- | [`HttpClientConfiguration`](lib/apimatic-core/http/configurations/http_client_configuration.rb) | Class used for configuring SDK by a user |
70
- | [`HttpRequest`](lib/apimatic-core/http/request/http_request.rb) | Class which contains information about the HTTP Request |
71
- | [`ApiResponse`](lib/apimatic-core/http/response/api_response.rb) | Wrapper class for Api Response |
72
- | [`HttpResponse`](lib/apimatic-core/http/response/http_response.rb) | Class which contains information about the HTTP Response |
73
- | [`HttpCallContext`](lib/apimatic-core/http/http_call_context.rb) | This class captures the HTTP request and response lifecycle during an API call and is used with clients or controllers that support pre- and post-request hooks. |
73
+ | Name | Description |
74
+ |--------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
75
+ | [`HttpRequest`](lib/apimatic-core/http/request/http_request.rb) | Class which contains information about the HTTP Request |
76
+ | [`ApiResponse`](lib/apimatic-core/http/response/api_response.rb) | Wrapper class for Api Response |
77
+ | [`HttpResponse`](lib/apimatic-core/http/response/http_response.rb) | Class which contains information about the HTTP Response |
78
+ | [`HttpCallContext`](lib/apimatic-core/http/http_call_context.rb) | This class captures the HTTP request and response lifecycle during an API call and is used with clients or controllers that support pre- and post-request hooks. |
74
79
 
75
80
  ## Logger
76
81
  | Name | Description |
@@ -119,6 +124,15 @@ gem 'apimatic_core'
119
124
  | [`JsonPointerHelper`](lib/apimatic-core/utilities/json_pointer_helper.rb ) | Utility methods for getting and setting JSON pointer values in hashes. |
120
125
  | [`JsonPointer`](lib/apimatic-core/utilities/json_pointer.rb ) | Enables querying, updating, and deleting values in nested Ruby Hashes and Arrays using JSON Pointer syntax (RFC 6901). |
121
126
  | [`LoggerHelper`](lib/apimatic-core/utilities/logger_helper.rb ) | Utility methods for logging. |
127
+ | [`RackRequestHelper`](lib/apimatic-core/utilities/rack_request_helper.rb ) | Utility methods for Rack::Request. |
128
+
129
+ ## Signature Verification
130
+ | Name | Description |
131
+ |---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
132
+ | [`HmacSignatureVerifier`](lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb) | Verifies HMAC signatures using configurable templates, hash algorithms, and encoders. |
133
+ | [`HexEncoder`](lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb) | Encodes digest as lowercase hex. |
134
+ | [`Base64Encoder`](lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb) | Encodes digest as Base64. |
135
+ | [`Base64UrlEncoder`](lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb) | Encodes digest as URL-safe Base64 without padding. |
122
136
 
123
137
  ## Links
124
138
  * [apimatic_core_interfaces](https://rubygems.org/gems/apimatic_core_interfaces)
@@ -16,12 +16,13 @@ module CoreLibrary
16
16
  # @param [Boolean] verify Should verification be enabled.
17
17
  # @param http_callback A method to be used as http callback
18
18
  # @param [HttpClient] http_client An instance of HttpClient
19
+ # @param [ProxySettings] proxy_settings The configurable settings for proxy
19
20
  def initialize(
20
21
  connection: nil, adapter: :net_http_persistent, timeout: 60,
21
22
  max_retries: 0, retry_interval: 1, backoff_factor: 2,
22
23
  retry_statuses: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
23
24
  retry_methods: %i[get put], cache: false, verify: true, http_callback: nil, http_client: nil,
24
- logging_configuration: nil
25
+ logging_configuration: nil, proxy_settings: nil
25
26
  )
26
27
  @response_factory = HttpResponseFactory.new
27
28
  @connection = connection
@@ -37,6 +38,7 @@ module CoreLibrary
37
38
  @cache = cache
38
39
  @http_client = http_client
39
40
  @logging_configuration = logging_configuration
41
+ @proxy_settings = proxy_settings
40
42
  end
41
43
 
42
44
  # Setter for http_client.
@@ -58,7 +60,8 @@ module CoreLibrary
58
60
  verify: @verify,
59
61
  http_callback: http_callback || DeepCloneUtils.deep_copy(@http_callback),
60
62
  http_client: DeepCloneUtils.deep_copy(@http_client),
61
- logging_configuration: DeepCloneUtils.deep_copy(@logging_configuration)
63
+ logging_configuration: DeepCloneUtils.deep_copy(@logging_configuration),
64
+ proxy_settings: DeepCloneUtils.deep_copy(@proxy_settings)
62
65
  )
63
66
  end
64
67
  end
@@ -0,0 +1,42 @@
1
+ module CoreLibrary
2
+ ##
3
+ # ProxySettings encapsulates HTTP proxy configuration for Faraday,
4
+ # including optional basic authentication.
5
+ #
6
+ class ProxySettings
7
+ attr_accessor :address, :port, :username, :password
8
+
9
+ ##
10
+ # @param address [String] The proxy server address (e.g., 'http://localhost').
11
+ # @param port [Integer, nil] Optional proxy server port (e.g., 8080).
12
+ # @param username [String, nil] Optional proxy auth username.
13
+ # @param password [String, nil] Optional proxy auth password.
14
+ #
15
+ # @raise [ArgumentError] If address is invalid.
16
+ #
17
+ def initialize(address:, port: nil, username: nil, password: nil)
18
+ raise ArgumentError, 'Proxy address must be a non-empty string' unless address.is_a?(String) && !address.empty?
19
+
20
+ @address = address
21
+ @port = port
22
+ @username = username
23
+ @password = password
24
+ end
25
+
26
+ ##
27
+ # Converts the proxy settings into a Faraday-compatible hash.
28
+ #
29
+ # @return [Hash] A hash with keys :uri, :user, and :password (as applicable).
30
+ #
31
+ def to_h
32
+ uri_str = port ? "#{address}:#{port}" : address
33
+
34
+ {
35
+ uri: uri_str
36
+ }.tap do |hash|
37
+ hash[:user] = username if username
38
+ hash[:password] = password if password
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module CoreLibrary
5
+ # === DigestEncoder Interface ===
6
+ class DigestEncoder
7
+ # Encodes a digest (bytes) into a string.
8
+ # @param digest [String] raw digest bytes
9
+ # @return [String]
10
+ def encode(digest)
11
+ raise NotImplementedError, 'This method must be implemented in a subclass.'
12
+ end
13
+ end
14
+
15
+ # === HexEncoder ===
16
+ class HexEncoder < DigestEncoder
17
+ def encode(digest)
18
+ digest.unpack1('H*')
19
+ end
20
+ end
21
+
22
+ # === Base64Encoder ===
23
+ class Base64Encoder < DigestEncoder
24
+ def encode(digest)
25
+ Base64.strict_encode64(digest)
26
+ end
27
+ end
28
+
29
+ # === Base64UrlEncoder ===
30
+ class Base64UrlEncoder < DigestEncoder
31
+ def encode(digest)
32
+ Base64.urlsafe_encode64(digest).delete('=')
33
+ end
34
+ end
35
+
36
+ # === HmacSignatureVerifier ===
37
+ # Verifies HMAC signatures for incoming requests.
38
+ #
39
+ # Works with Rack::Request or any object exposing Rack-like `env` or `headers`/`raw_body`.
40
+ #
41
+ # Example:
42
+ # verifier = HmacSignatureVerifier.new(
43
+ # secret_key: "supersecret",
44
+ # signature_header: "X-Signature"
45
+ # )
46
+ # result = verifier.verify(rack_request)
47
+ #
48
+ class HmacSignatureVerifier < CoreLibrary::SignatureVerifier
49
+ def initialize(secret_key:, signature_header:, canonical_message_builder: nil, hash_algorithm: 'sha256',
50
+ encoder: HexEncoder.new, signature_value_template: DIGEST_PLACEHOLDER)
51
+ raise ArgumentError, 'secret_key must be a non-empty string' unless secret_key.is_a?(String) && !secret_key.empty?
52
+
53
+ unless signature_header.is_a?(String) && !signature_header.strip.empty?
54
+ raise ArgumentError,
55
+ 'signature_header must be a non-empty string'
56
+ end
57
+
58
+ @secret_key = secret_key
59
+ @signature_header_lc = signature_header.strip.downcase.tr('_', '-')
60
+ @canonical_message_builder = canonical_message_builder
61
+ @hash_alg = hash_algorithm
62
+ @encoder = encoder
63
+ @signature_value_template = signature_value_template
64
+ end
65
+
66
+ # Verifies the HMAC signature for the request.
67
+ #
68
+ # @param request [Rack::Request, #env, #headers, #raw_body]
69
+ # @return [CoreLibrary::SignatureVerificationResult]
70
+ def verify(request)
71
+ headers = RackRequestHelper.extract_headers_hash(request)
72
+ provided_signature = headers[@signature_header_lc]
73
+
74
+ if provided_signature.nil?
75
+ return CoreLibrary::SignatureVerificationResult.failed(
76
+ ["Signature header '#{@signature_header_lc}' is missing"]
77
+ )
78
+ end
79
+
80
+ message = resolve_message_bytes(request)
81
+ digest = OpenSSL::HMAC.digest(@hash_alg, @secret_key, message)
82
+ encoded_digest = @encoder.encode(digest) unless @encoder.nil?
83
+
84
+ expected_signature =
85
+ if @signature_value_template.include?(DIGEST_PLACEHOLDER)
86
+ @signature_value_template.gsub(DIGEST_PLACEHOLDER, encoded_digest)
87
+ else
88
+ @signature_value_template
89
+ end
90
+
91
+ if HmacSignatureVerifier.fixed_length_secure_compare(provided_signature, expected_signature)
92
+ CoreLibrary::SignatureVerificationResult.passed
93
+ else
94
+ CoreLibrary::SignatureVerificationResult.failed(
95
+ ['Signature mismatch']
96
+ )
97
+ end
98
+ rescue StandardError => e
99
+ CoreLibrary::SignatureVerificationResult.failed(
100
+ ["Signature verification failed: #{e.message}"]
101
+ )
102
+ end
103
+
104
+ # Compares two strings in constant time to prevent timing attacks.
105
+ # Uses OpenSSL.fixed_length_secure_compare when available (Ruby ≥ 3
106
+ # with openssl gem ≥ 3.x); falls back to a pure Ruby implementation otherwise.
107
+ #
108
+ # @param a [String] the first string to compare
109
+ # @param b [String] the second string to compare
110
+ # @return [Boolean] true if the strings are equal, false otherwise
111
+ # @raise [ArgumentError] if the inputs are of unequal length
112
+ def self.fixed_length_secure_compare(a, b)
113
+ if RUBY_VERSION >= '3.0' && OpenSSL.respond_to?(:fixed_length_secure_compare)
114
+ OpenSSL.fixed_length_secure_compare(a, b)
115
+ else
116
+ raise ArgumentError, 'inputs must be same length' unless a.bytesize == b.bytesize
117
+
118
+ res = 0
119
+ a.bytes.zip(b.bytes) { |x, y| res |= (x ^ y) }
120
+ res.zero?
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ # Builds the canonical message (raw body or custom builder)
127
+ def resolve_message_bytes(request)
128
+ if @canonical_message_builder.nil?
129
+ RackRequestHelper.read_raw_body(request)
130
+ else
131
+ result = @canonical_message_builder.call(request)
132
+ result.to_s
133
+ end
134
+ end
135
+
136
+ DIGEST_PLACEHOLDER = '{digest}'.freeze
137
+ end
138
+ end
@@ -0,0 +1,93 @@
1
+ module CoreLibrary
2
+ # === SignatureVerifierHelper ===
3
+ # Utility class for extracting headers and body from Rack::Request (or request-like objects).
4
+ #
5
+ # All methods are class methods and can be used without instantiating.
6
+ class RackRequestHelper
7
+ class << self
8
+ # Extract headers into a downcast-key Hash from Rack::Request or request-like object.
9
+ # Supports both Rack env (`request.env`) and objects exposing `headers` Hash.
10
+ #
11
+ # @param request [Rack::Request, #env, #headers]
12
+ # @return [Hash{String => String}]
13
+ def extract_headers_hash(request)
14
+ env = if request.respond_to?(:env)
15
+ request.env
16
+ elsif request.respond_to?(:headers) && request.headers.is_a?(Hash)
17
+ headers_to_env(request.headers)
18
+ else
19
+ {}
20
+ end
21
+
22
+ env.each_with_object({}) do |(k, v), acc|
23
+ next if v.nil?
24
+
25
+ key = k.to_s
26
+ if key.start_with?('HTTP_') || %w[CONTENT_TYPE CONTENT_LENGTH].include?(key)
27
+ header_name = format_header_name(key)
28
+ acc[header_name.downcase] = v.to_s
29
+ end
30
+ end
31
+ end
32
+
33
+ # Read raw body from a Rack::Request (rewinds after reading).
34
+ # Falls back to `raw_body` if provided.
35
+ #
36
+ # @param request [Rack::Request, #body, #raw_body]
37
+ # @return [String]
38
+ def read_raw_body(request)
39
+ if request.respond_to?(:body) && request.body.respond_to?(:read)
40
+ body = request.body.read
41
+ request.body.rewind if request.body.respond_to?(:rewind)
42
+ body
43
+ elsif request.respond_to?(:raw_body)
44
+ request.raw_body.to_s
45
+ else
46
+ ''.dup
47
+ end
48
+ end
49
+
50
+ # Normalizes a header name to the Rack environment variable format.
51
+ #
52
+ # - Converts "Content-Type" → "CONTENT_TYPE"
53
+ # - Converts "Content-Length" → "CONTENT_LENGTH"
54
+ # - Converts all other headers → "HTTP_<UPPERCASED_NAME>"
55
+ #
56
+ # @param [String, Symbol] name The header name.
57
+ # @return [String] The Rack-compliant environment key.
58
+ def prepend_header(name)
59
+ k_s = name.to_s
60
+ if /\Acontent[-_]type\z/i.match?(k_s)
61
+ 'CONTENT_TYPE'
62
+ elsif /\Acontent[-_]length\z/i.match?(k_s)
63
+ 'CONTENT_LENGTH'
64
+ else
65
+ "HTTP_#{k_s.upcase.gsub('-', '_')}"
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Convert a simple headers Hash (e.g., { "Content-Type" => "a" }) into Rack-style env.
72
+ #
73
+ # @param headers_hash [Hash]
74
+ # @return [Hash]
75
+ def headers_to_env(headers_hash)
76
+ headers_hash.transform_keys do |k|
77
+ prepend_header(k)
78
+ end
79
+ end
80
+
81
+ # Convert Rack env key (eg. 'HTTP_X_TIMESTAMP') into header name ('X-Timestamp')
82
+ #
83
+ # @param raw [String]
84
+ # @return [String]
85
+ def format_header_name(raw)
86
+ raw.to_s.sub(/^HTTP_/, '')
87
+ .split('_')
88
+ .map(&:capitalize)
89
+ .join('-')
90
+ end
91
+ end
92
+ end
93
+ end
data/lib/apimatic_core.rb CHANGED
@@ -30,6 +30,7 @@ require_relative 'apimatic-core/pagination/strategies/offset_pagination'
30
30
  require_relative 'apimatic-core/pagination/strategies/page_pagination'
31
31
 
32
32
  require_relative 'apimatic-core/http/configurations/http_client_configuration'
33
+ require_relative 'apimatic-core/http/configurations/proxy_settings'
33
34
  require_relative 'apimatic-core/http/request/http_request'
34
35
  require_relative 'apimatic-core/http/response/http_response'
35
36
  require_relative 'apimatic-core/http/response/api_response'
@@ -60,6 +61,7 @@ require_relative 'apimatic-core/utilities/logger_helper'
60
61
  require_relative 'apimatic-core/utilities/constants'
61
62
  require_relative 'apimatic-core/utilities/deep_clone_utils'
62
63
  require_relative 'apimatic-core/utilities/json_pointer'
64
+ require_relative 'apimatic-core/utilities/rack_request_helper'
63
65
 
64
66
  require_relative 'apimatic-core/authentication/header_auth'
65
67
  require_relative 'apimatic-core/authentication/query_auth'
@@ -67,3 +69,5 @@ require_relative 'apimatic-core/authentication/multiple/auth_group'
67
69
  require_relative 'apimatic-core/authentication/multiple/and_auth_group'
68
70
  require_relative 'apimatic-core/authentication/multiple/or_auth_group'
69
71
  require_relative 'apimatic-core/authentication/multiple/single_auth'
72
+
73
+ require_relative 'apimatic-core/security/signature_verification/hmac_signature_verifier'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apimatic_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.18
4
+ version: 0.3.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - APIMatic Ltd.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-11 00:00:00.000000000 Z
11
+ date: 2025-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: apimatic_core_interfaces
@@ -170,6 +170,7 @@ files:
170
170
  - lib/apimatic-core/exceptions/one_of_validation_exception.rb
171
171
  - lib/apimatic-core/factories/http_response_factory.rb
172
172
  - lib/apimatic-core/http/configurations/http_client_configuration.rb
173
+ - lib/apimatic-core/http/configurations/proxy_settings.rb
173
174
  - lib/apimatic-core/http/http_call_context.rb
174
175
  - lib/apimatic-core/http/request/http_request.rb
175
176
  - lib/apimatic-core/http/response/api_response.rb
@@ -186,6 +187,7 @@ files:
186
187
  - lib/apimatic-core/pagination/strategies/page_pagination.rb
187
188
  - lib/apimatic-core/request_builder.rb
188
189
  - lib/apimatic-core/response_handler.rb
190
+ - lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb
189
191
  - lib/apimatic-core/types/error_case.rb
190
192
  - lib/apimatic-core/types/parameter.rb
191
193
  - lib/apimatic-core/types/sdk/api_exception.rb
@@ -207,6 +209,7 @@ files:
207
209
  - lib/apimatic-core/utilities/json_pointer.rb
208
210
  - lib/apimatic-core/utilities/json_pointer_helper.rb
209
211
  - lib/apimatic-core/utilities/logger_helper.rb
212
+ - lib/apimatic-core/utilities/rack_request_helper.rb
210
213
  - lib/apimatic-core/utilities/union_type_helper.rb
211
214
  - lib/apimatic-core/utilities/xml_helper.rb
212
215
  - lib/apimatic_core.rb