gapic-common 1.3.0 → 1.4.1

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: e4338a183319704aa9fb613e73d34b4092a94ad56f3136c2d4a0490a3218770f
4
- data.tar.gz: 3a0d1456f8c2bc573245e5c46afae6b2c83be393085bf6bebc5b28c06368d3f7
3
+ metadata.gz: 11f04f82fc6e98e5ca24d814d6162d68681999b6a7f2305cc32c77c597db2a88
4
+ data.tar.gz: a36c758ffd65878ac9809c0ecfc515e2c6e0e817f5ad679e8574e5dbeb8a03f6
5
5
  SHA512:
6
- metadata.gz: 696026e2b9ca26c060c2b65a336f790ec193938a7740d8bdce3008c6bfae40cd80290077b717174554d7e6a1530e470f2523a5859b210c2bf9bd583e7dd72041
7
- data.tar.gz: 42979948486f1e55f990bdbcc0ee2a697ffbf3c450b4013a7ae293e0960291533bfc4356fcfb8cfdeca5eae94d97a7ff522e2b5667cd1e9611de93cf39af560e
6
+ metadata.gz: 8fb7d4c578dc4b44383ac18e33159fd824465e63df13b10ed2d2fe142c4a9743876d7a3a41cd00680dc3e44cab88f6d3d6185198a8b74a85ace1ff30ee6d9e4e
7
+ data.tar.gz: 3d508937a578e43a1fe8022557d08a7c01be477b9141a1d055c0d6a1df8e739eca0a9e2f14d178aa90c4b121d44dd12e437d73242250009916645948238af388
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Release History
2
2
 
3
+ ### 1.4.1 (2026-09-28)
4
+
5
+ #### Bug Fixes
6
+
7
+ * validate path parameters and prevent traversal/injection in REST transcoder ([#67](https://github.com/googleapis/ruby-core-libraries/issues/67))
8
+
9
+ ### 1.4.0 (2026-09-23)
10
+
11
+ #### Features
12
+
13
+ * add custom predicate matching to RetryPolicy ([#59](https://github.com/googleapis/ruby-core-libraries/issues/59))
14
+ #### Bug Fixes
15
+
16
+ * require grpc >= 1.83 for post-quantum key exchange ([#73](https://github.com/googleapis/ruby-core-libraries/issues/73))
17
+ * only use emit_default for proto messages ([#75](https://github.com/googleapis/ruby-core-libraries/issues/75))
18
+
3
19
  ### 1.3.0 (2026-03-05)
4
20
 
5
21
  #### Features
data/README.md CHANGED
@@ -15,7 +15,7 @@ convenient and idiomatic API surface to callers.
15
15
 
16
16
  ## Supported Ruby Versions
17
17
 
18
- This library is supported on Ruby 3.0+.
18
+ This library is supported on Ruby 3.2+.
19
19
 
20
20
  Google provides official support for Ruby versions that are actively supported
21
21
  by Ruby Core—that is, Ruby versions that are either in normal maintenance or in
@@ -24,6 +24,35 @@ work, but are unsupported and not recommended. See
24
24
  https://www.ruby-lang.org/en/downloads/branches/ for details about the Ruby
25
25
  support schedule.
26
26
 
27
+ ## Post-Quantum Key Exchange
28
+
29
+ Clients built on this library negotiate the hybrid post-quantum key exchange
30
+ group `X25519MLKEM768` — classical X25519 paired with ML-KEM-768 ([NIST FIPS
31
+ 203][]) — over TLS 1.3. No application code changes are required, because the
32
+ handshake belongs to the transport layer. The two transports source that
33
+ support differently:
34
+
35
+ | Transport | Cryptographic provider | Requirement | Enforced by this gem? |
36
+ | --- | --- | --- | --- |
37
+ | gRPC | BoringSSL, vendored inside the `grpc` gem | `grpc >= 1.83` | Yes |
38
+ | REST | Host system OpenSSL, via `Net::HTTP` and Faraday | OpenSSL `>= 3.5` | No |
39
+
40
+ gRPC clients get post-quantum key exchange automatically: `gapic-common`
41
+ depends on `grpc >= 1.83`, the first release to offer `X25519MLKEM768` in the
42
+ TLS ClientHello by default.
43
+
44
+ The REST requirement cannot be expressed as a gem dependency. Ruby's `openssl`
45
+ is a default gem bound to whatever `libssl` the host provides, and ML-KEM first
46
+ ships in OpenSSL 3.5. On an older host, REST connections negotiate classical
47
+ X25519 instead — a safe fallback rather than an error, but not post-quantum. To
48
+ check the OpenSSL your Ruby is linked against:
49
+
50
+ ```sh
51
+ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION'
52
+ ```
53
+
54
+ [NIST FIPS 203]: https://csrc.nist.gov/pubs/fips/203/final
55
+
27
56
  ## Contributing
28
57
 
29
58
  Contributions to this library are always welcome and highly encouraged.
@@ -45,9 +45,17 @@ module Gapic
45
45
  # @param retry_codes [Array<String|Integer>] List of retry codes.
46
46
  # @param timeout [Numeric] Timeout threshold value in seconds.
47
47
  # @param jitter [Numeric] Random jitter added to the delay in seconds.
48
+ # @param retry_predicate [Proc, nil] The predicate to evaluate whether to retry on a given error. Optional.
49
+ # If the predicate is specified, it is run first. If it returns nil, the decision on
50
+ # whether to retry is made on the basis of retry_codes. Otherwise, the truthiness of
51
+ # the return value determines whether to retry.
48
52
  #
49
- def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil, jitter: nil
53
+ def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil,
54
+ jitter: nil, retry_predicate: nil
50
55
  raise ArgumentError, "jitter cannot be negative" if jitter&.negative?
56
+ if retry_predicate && !retry_predicate.respond_to?(:call)
57
+ raise ArgumentError, "retry_predicate must respond to :call"
58
+ end
51
59
 
52
60
  # Instance values are set as `nil` to determine whether values are overriden from default.
53
61
  @initial_delay = initial_delay
@@ -56,6 +64,7 @@ module Gapic
56
64
  @retry_codes = convert_codes retry_codes
57
65
  @timeout = timeout
58
66
  @jitter = jitter
67
+ @retry_predicate = retry_predicate
59
68
  start!
60
69
  end
61
70
 
@@ -89,6 +98,19 @@ module Gapic
89
98
  @jitter || DEFAULT_JITTER
90
99
  end
91
100
 
101
+ ##
102
+ # The predicate to evaluate whether to retry on a given error. Optional.
103
+ #
104
+ # When a predicate is specified:
105
+ # 1. The predicate is executed first on the error.
106
+ # 2. If it returns nil, the decision on whether to retry is made on the basis of retry_codes.
107
+ # 3. Otherwise, the truthiness of the return value determines whether to retry.
108
+ #
109
+ # @return [Proc, nil]
110
+ def retry_predicate
111
+ @retry_predicate
112
+ end
113
+
92
114
  ##
93
115
  # Returns a duplicate in a non-executing state, i.e. with the deadline
94
116
  # and current delay reset.
@@ -101,7 +123,8 @@ module Gapic
101
123
  multiplier: @multiplier,
102
124
  retry_codes: @retry_codes,
103
125
  timeout: @timeout,
104
- jitter: @jitter
126
+ jitter: @jitter,
127
+ retry_predicate: @retry_predicate
105
128
  end
106
129
 
107
130
  ##
@@ -176,6 +199,13 @@ module Gapic
176
199
  # @return [Boolean] Whether this error should be retried.
177
200
  #
178
201
  def retry_error? error
202
+ # Run predicate first. If it returns nil, decision is made on the basis of retry_codes.
203
+ # Otherwise return truthiness.
204
+ if @retry_predicate.respond_to? :call
205
+ result = @retry_predicate.call error
206
+ return !!result unless result.nil?
207
+ end
208
+
179
209
  (defined?(::GRPC) && error.is_a?(::GRPC::BadStatus) && retry_codes.include?(error.code)) ||
180
210
  (error.respond_to?(:response_status) &&
181
211
  retry_codes.include?(ErrorCodes.grpc_error_for(error.response_status)))
@@ -197,11 +227,12 @@ module Gapic
197
227
  #
198
228
  def apply_defaults retry_policy
199
229
  return unless retry_policy.is_a? Hash
200
- @retry_codes ||= convert_codes retry_policy[:retry_codes]
201
- @initial_delay ||= retry_policy[:initial_delay]
202
- @multiplier ||= retry_policy[:multiplier]
203
- @max_delay ||= retry_policy[:max_delay]
204
- @jitter ||= retry_policy[:jitter]
230
+ @retry_codes ||= convert_codes retry_policy[:retry_codes]
231
+ @initial_delay ||= retry_policy[:initial_delay]
232
+ @multiplier ||= retry_policy[:multiplier]
233
+ @max_delay ||= retry_policy[:max_delay]
234
+ @jitter ||= retry_policy[:jitter]
235
+ @retry_predicate ||= retry_policy[:retry_predicate]
205
236
 
206
237
  self
207
238
  end
@@ -214,13 +245,14 @@ module Gapic
214
245
  other.multiplier == multiplier &&
215
246
  other.retry_codes == retry_codes &&
216
247
  other.timeout == timeout &&
217
- other.jitter == jitter
248
+ other.jitter == jitter &&
249
+ other.retry_predicate == retry_predicate
218
250
  end
219
251
  alias == eql?
220
252
 
221
253
  # @private Hash code
222
254
  def hash
223
- [initial_delay, max_delay, multiplier, retry_codes, timeout, jitter].hash
255
+ [initial_delay, max_delay, multiplier, retry_codes, timeout, jitter, retry_predicate].hash
224
256
  end
225
257
 
226
258
  private
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Gapic
16
16
  module Common
17
- VERSION = "1.3.0".freeze
17
+ VERSION = "1.4.1".freeze
18
18
  end
19
19
  end
@@ -111,12 +111,14 @@ module Gapic
111
111
  # @return [Hash{String, String}]
112
112
  # Name to value hash of the variables for the uri template expansion.
113
113
  # The values are percent-escaped with slashes potentially preserved.
114
+ # @raise [Gapic::Common::Error] If any parameter value fails path traversal or injection validation.
114
115
  def bind_uri_values! http_binding, request_hash
115
116
  http_binding.field_bindings.to_h do |field_binding|
116
117
  field_path_camel = field_binding.field_path.split(".").map { |part| camel_name_for part }.join(".")
117
118
  field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
118
119
 
119
120
  if field_value
121
+ validate_field_binding! field_binding, field_value
120
122
  field_value = field_value.split("/").map { |segment| percent_escape segment }.join("/")
121
123
  end
122
124
 
@@ -124,6 +126,43 @@ module Gapic
124
126
  end
125
127
  end
126
128
 
129
+ # Validates a user-supplied parameter value bound to a standard (*) or path (**) URI template variable
130
+ # to prevent directory traversal exploits.
131
+ #
132
+ # @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
133
+ # @param field_value [String] The parameter value to validate.
134
+ # @raise [Gapic::Common::Error] If validation fails.
135
+ def validate_field_binding! field_binding, field_value
136
+ validate_path_binding! field_binding, field_value
137
+ end
138
+
139
+ # Validates standard (*) and path (**) parameters by ensuring that no segment in the parameter
140
+ # value is a directory traversal segment (. or ..).
141
+ #
142
+ # Validation Mechanism:
143
+ # 1. Splits the parameter value by slash (`/`) using `-1` limit to preserve all segments.
144
+ # 2. Checks each segment. If any segment matches `.` or `..`, it immediately raises
145
+ # a `Gapic::Common::Error`, aborting the request.
146
+ # 3. Empty segments (e.g. duplicate slashes `//` or trailing slashes `/`) are allowed
147
+ # by this validator and passed to the server, which handles normalization or returns 400.
148
+ #
149
+ # @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
150
+ # @param field_value [String] The parameter value to validate.
151
+ # @raise [Gapic::Common::Error] If validation fails.
152
+ def validate_path_binding! field_binding, field_value
153
+ segments = field_value.split("/", -1)
154
+ segments.each do |segment|
155
+ next unless [".", ".."].include? segment
156
+ if field_binding.preserve_slashes
157
+ raise ::Gapic::Common::Error,
158
+ "Value for #{field_binding.field_path} must not contain segments that are exactly '#{segment}'."
159
+ else
160
+ raise ::Gapic::Common::Error,
161
+ "Invalid value for #{field_binding.field_path} '#{segment}'."
162
+ end
163
+ end
164
+ end
165
+
127
166
  # Percent-escapes a string.
128
167
  # @param str [String] String to escape.
129
168
  # @return [String] Escaped string.
@@ -160,7 +199,16 @@ module Gapic
160
199
  request_body_field = request.send body_template.to_sym if request.respond_to? body_template.to_sym
161
200
  if request_body_field
162
201
  request_hash_without_uri.delete camel_name_for body_template
163
- body = request_body_field.to_json emit_defaults: true
202
+ # `emit_defaults` is only meaningful for, and only accepted by,
203
+ # `Google::Protobuf::MessageExts#to_json`. When the body template points at a
204
+ # scalar, enum or repeated field, `to_json` resolves to the json gem's
205
+ # `Object#to_json`, which has never accepted the keyword: json 2.x silently
206
+ # discarded unknown keywords, json 3.x raises ArgumentError.
207
+ body = if request_body_field.is_a? ::Google::Protobuf::MessageExts
208
+ request_body_field.to_json emit_defaults: true
209
+ else
210
+ request_body_field.to_json
211
+ end
164
212
  end
165
213
 
166
214
  query_params = build_query_params request_hash_without_uri
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gapic-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google API Authors
@@ -137,16 +137,22 @@ dependencies:
137
137
  name: grpc
138
138
  requirement: !ruby/object:Gem::Requirement
139
139
  requirements:
140
- - - "~>"
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '1.83'
143
+ - - "<"
141
144
  - !ruby/object:Gem::Version
142
- version: '1.66'
145
+ version: 2.a
143
146
  type: :runtime
144
147
  prerelease: false
145
148
  version_requirements: !ruby/object:Gem::Requirement
146
149
  requirements:
147
- - - "~>"
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '1.83'
153
+ - - "<"
148
154
  - !ruby/object:Gem::Version
149
- version: '1.66'
155
+ version: 2.a
150
156
  email:
151
157
  - googleapis-packages@google.com
152
158
  executables: []
@@ -213,7 +219,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
219
  requirements:
214
220
  - - ">="
215
221
  - !ruby/object:Gem::Version
216
- version: '3.1'
222
+ version: '3.2'
217
223
  required_rubygems_version: !ruby/object:Gem::Requirement
218
224
  requirements:
219
225
  - - ">="