entitlements-app 1.2.7 → 1.2.8

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: 0d3e0f1ede9a8b516f1553ef660e1926f99110baa8958e7049d2b5306576d9d3
4
- data.tar.gz: 53260170e02ec2d3f21051e2d931b94b285d39fd9622e1f297e09a63df79c55e
3
+ metadata.gz: fdad062839c6178d6b23880ddc7c7392b5023d7f783e7b3686c3c3456392ff62
4
+ data.tar.gz: 924425bb54c9ffed6a6da2ff0a363e933b2351b45c2a4fa9c522ba5eb41fce39
5
5
  SHA512:
6
- metadata.gz: 88c17c22c777d419fb095c24c488be663090c2d0ba00047b87add8d84c57769e80fe304a4f1365fe360c52cf0d00b6400c23907495b9d838ef85eebb3482b021
7
- data.tar.gz: 7702ff6718762aa5b560582e39f7a141dbcae04c5eafed1189cdb617dc5411a038694f1325229e382a97df463479dd8cb64d4e5c58c59536637efb94bfbe610d
6
+ metadata.gz: 74c0f06e2255e56c9a59f3a8bb6227a674b5a296ca310703c681434c78c210f658509689e41306910dfd41ae4a3247b5aeb7998654f05d17737d154fa98b453b
7
+ data.tar.gz: 10b6e365e40220d1c6687ded47c603b1a2b968ccdea7dc35fa61842743668d46466359f4dc954217bed2efcf13751f419cfb6133285e5eb3370a60cbc1a26f62
@@ -8,6 +8,8 @@ module Entitlements
8
8
  include ::Contracts::Core
9
9
  C = ::Contracts
10
10
 
11
+ MAX_CONNECTION_ATTEMPTS = 3
12
+
11
13
  class ConnectionError < RuntimeError; end
12
14
  class DuplicateEntryError < RuntimeError; end
13
15
  class EntryError < RuntimeError; end
@@ -120,27 +122,33 @@ module Entitlements
120
122
  # be consistent no matter what. LDAP is case insensitive after all!
121
123
  downcased_attrs = attrs == "*" ? "*" : attrs.map { |a| a.downcase }
122
124
 
123
- result = {}
124
- search_succeeded = ldap.search(
125
- base: base,
126
- filter: filter,
127
- attributes: downcased_attrs,
128
- scope: scope,
129
- return_result: false
130
- ) do |entry|
131
- result_key = index == :dn ? entry.dn : entry[index]
132
- unless result_key
133
- raise EntryError, "#{entry.dn} has no value for #{index.inspect}"
134
- end
125
+ # Outside the retry block: `ldap` retries its own bind, and nesting would multiply attempts.
126
+ connection = ldap
127
+ result = with_ldap_connection_retries do
128
+ entries = {}
129
+ search_succeeded = connection.search(
130
+ base: base,
131
+ filter: filter,
132
+ attributes: downcased_attrs,
133
+ scope: scope,
134
+ return_result: false
135
+ ) do |entry|
136
+ result_key = index == :dn ? entry.dn : entry[index]
137
+ unless result_key
138
+ raise EntryError, "#{entry.dn} has no value for #{index.inspect}"
139
+ end
140
+
141
+ if entries.key?(result_key)
142
+ other_entry_dn = entries[result_key].dn
143
+ raise DuplicateEntryError, "#{entry.dn} and #{other_entry_dn} have the same value of #{index} = #{result_key.inspect}"
144
+ end
135
145
 
136
- if result.key?(result_key)
137
- other_entry_dn = result[result_key].dn
138
- raise DuplicateEntryError, "#{entry.dn} and #{other_entry_dn} have the same value of #{index} = #{result_key.inspect}"
146
+ entries[result_key] = entry
139
147
  end
148
+ remember_existing_dn(base) if search_succeeded
140
149
 
141
- result[result_key] = entry
150
+ entries
142
151
  end
143
- remember_existing_dn(base) if search_succeeded
144
152
 
145
153
  Entitlements.logger.debug "Completed search: #{result.keys.size} result(s)"
146
154
 
@@ -248,7 +256,7 @@ module Entitlements
248
256
  # Returns a Net::LDAP object configured to connect and bind for each operation.
249
257
  Contract C::None => Net::LDAP
250
258
  def ldap
251
- @ldap ||= begin
259
+ @ldap ||= with_ldap_connection_retries do
252
260
  uri = URI(addr)
253
261
 
254
262
  # Construct the object
@@ -289,6 +297,40 @@ module Entitlements
289
297
  end
290
298
  end
291
299
 
300
+ def with_ldap_connection_retries
301
+ attempts = 0
302
+ begin
303
+ attempts += 1
304
+ yield
305
+ rescue SystemCallError, Net::LDAP::Error => error
306
+ raise unless retryable_connection_error?(error)
307
+
308
+ uri = URI(addr)
309
+ if attempts >= MAX_CONNECTION_ATTEMPTS
310
+ Entitlements.logger.error "LDAP connection to #{uri.host}:#{uri.port} failed after #{attempts} attempts: #{error.class}: #{error.message}"
311
+ raise
312
+ end
313
+
314
+ Entitlements.logger.warn "LDAP connection to #{uri.host}:#{uri.port} failed (attempt #{attempts} of #{MAX_CONNECTION_ATTEMPTS}): #{error.class}: #{error.message}. Retrying in #{attempts}s."
315
+ sleep attempts
316
+ retry
317
+ end
318
+ end
319
+
320
+ # net-ldap turns single-host connection-setup failures (DNS, connect/TLS timeouts,
321
+ # unreachable host) into a bare Net::LDAP::Error with no cause, and the messages vary
322
+ # by libc. Operation errors are subclasses. Errno cases can hit mid-request; that's
323
+ # fine only because this never wraps writes.
324
+ def retryable_connection_error?(error)
325
+ case error
326
+ when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT
327
+ true
328
+ when Net::LDAP::Error
329
+ error.instance_of?(Net::LDAP::Error) &&
330
+ !error.message.match?(/certificate verify failed|does not match the server certificate/)
331
+ end
332
+ end
333
+
292
334
  # Create an object that does not exist. Private method intended to be called from
293
335
  # "upsert" after determining that the object doesn't exist.
294
336
  #
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Entitlements
4
4
  module Version
5
- VERSION = "1.2.7"
5
+ VERSION = "1.2.8"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entitlements-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.7
4
+ version: 1.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub, Inc. Security Ops
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-14 00:00:00.000000000 Z
11
+ date: 2026-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby