entitlements-app 1.2.11 → 1.2.12

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: 818e69daf99d90337415063f3d17ce109739eafbbb7114f708e9a21186826cfd
4
- data.tar.gz: dbb843bad39ac3db66ad824f14f7fde164adbf8327a013d0e4d8aab8d9109c79
3
+ metadata.gz: 6d261fd97bc7b035820a02ef58cfd54b54678ca97bba95caa68625bfdf3f09a1
4
+ data.tar.gz: df68e3cf38d4602047b2fcb3e853a2d31ee10e6c5a71ffdead194824ef15f8e8
5
5
  SHA512:
6
- metadata.gz: cf6987a46b238eae111fe5ef9d290b6ae4b6f464f7853107066a4804011e7b426ca2080558f950c2c0976a0105cf26aedc3683bac4e72a3b24e3894be0999239
7
- data.tar.gz: 71395936262a5f88e3fd5ffafc1653cbd8c05ca59a4606f0473b4637594c192a4308ab4a3bdf56561d75df0deee167260666aa56ae5c10a6a9793d374ced5f4f
6
+ metadata.gz: 0cc730a8a9eb5f0cde4c638c9228bedb52c74fd8902d7af54ddee675fda12af8246159a54bfe33ce6440dbce500c3b5d463483d60b34ca23617981eedf265f6c
7
+ data.tar.gz: b9237fea7006e586a638e16ffeb0b71b8325cbee3126fe2cd5102a3b8ed286d9fccd7cbc4a6e54d01e0e70761cf31d1f957e54db5e5437d8c7a2ab8f46d85505
@@ -84,6 +84,8 @@ module Entitlements
84
84
  @person_dn_format = person_dn_format
85
85
  @known_existing_dns = {}
86
86
  @known_existing_dns_mutex = Mutex.new
87
+ @search_connections = {}
88
+ @search_connections_mutex = Mutex.new
87
89
  end
88
90
 
89
91
  # Read a single entry identified by its DN and return the value. Returns nil if
@@ -122,32 +124,32 @@ module Entitlements
122
124
  # be consistent no matter what. LDAP is case insensitive after all!
123
125
  downcased_attrs = attrs == "*" ? "*" : attrs.map { |a| a.downcase }
124
126
 
125
- # Outside the retry block: `ldap` retries its own bind, and nesting would multiply attempts.
126
- connection = ldap
127
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}"
128
+ with_search_connection do |connection|
129
+ entries = {}
130
+ search_succeeded = connection.search(
131
+ base: base,
132
+ filter: filter,
133
+ attributes: downcased_attrs,
134
+ scope: scope,
135
+ return_result: false
136
+ ) do |entry|
137
+ result_key = index == :dn ? entry.dn : entry[index]
138
+ unless result_key
139
+ raise EntryError, "#{entry.dn} has no value for #{index.inspect}"
140
+ end
141
+
142
+ if entries.key?(result_key)
143
+ other_entry_dn = entries[result_key].dn
144
+ raise DuplicateEntryError, "#{entry.dn} and #{other_entry_dn} have the same value of #{index} = #{result_key.inspect}"
145
+ end
146
+
147
+ entries[result_key] = entry
139
148
  end
149
+ remember_existing_dn(base) if search_succeeded
140
150
 
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
145
-
146
- entries[result_key] = entry
151
+ entries
147
152
  end
148
- remember_existing_dn(base) if search_succeeded
149
-
150
- entries
151
153
  end
152
154
 
153
155
  Entitlements.logger.debug "Completed search: #{result.keys.size} result(s)"
@@ -261,24 +263,6 @@ module Entitlements
261
263
 
262
264
  # Construct the object
263
265
  Entitlements.logger.debug "Creating connection to #{uri.host} port #{uri.port}"
264
- ldap_options = {
265
- host: uri.host,
266
- port: uri.port,
267
- auth: { method: :simple, username: binddn, password: bindpw }
268
- }
269
- if uri.scheme == "ldaps"
270
- ldap_options[:encryption] = {
271
- method: :simple_tls,
272
- tls_options: {
273
- verify_mode: @disable_ssl_verification ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
274
- }
275
- }
276
- end
277
-
278
- if @ca_file && ldap_options[:encryption].key?(:tls_options)
279
- ldap_options[:encryption][:tls_options][:ca_file] = @ca_file
280
- end
281
-
282
266
  ldap_object = Net::LDAP.new(ldap_options)
283
267
  raise WTFError, "FATAL: can't create LDAP connection object" if ldap_object.nil?
284
268
 
@@ -297,6 +281,78 @@ module Entitlements
297
281
  end
298
282
  end
299
283
 
284
+ def ldap_options
285
+ uri = URI(addr)
286
+ options = {
287
+ host: uri.host,
288
+ port: uri.port,
289
+ auth: { method: :simple, username: binddn, password: bindpw }
290
+ }
291
+ if uri.scheme == "ldaps"
292
+ options[:encryption] = {
293
+ method: :simple_tls,
294
+ tls_options: {
295
+ verify_mode: @disable_ssl_verification ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
296
+ }
297
+ }
298
+ end
299
+
300
+ if @ca_file && options[:encryption].key?(:tls_options)
301
+ options[:encryption][:tls_options][:ca_file] = @ca_file
302
+ end
303
+
304
+ options
305
+ end
306
+
307
+ # Searches reuse one bound connection per thread instead of connecting and binding each time.
308
+ def with_search_connection
309
+ reused = @search_connections_mutex.synchronize { @search_connections.key?(Thread.current) }
310
+ yield search_connection
311
+ rescue SystemCallError, IOError, OpenSSL::SSL::SSLError, Net::LDAP::Error
312
+ close_search_connection
313
+ # The server may have dropped an idle connection, so retry once on a new one.
314
+ reused ? retry : raise
315
+ end
316
+
317
+ def search_connection
318
+ existing = @search_connections_mutex.synchronize { @search_connections[Thread.current] }
319
+ return existing.first if existing
320
+
321
+ options = ldap_options
322
+ Entitlements.logger.debug "Creating connection to #{options[:host]} port #{options[:port]}"
323
+ connection = Net::LDAP::Connection.new(host: options[:host], port: options[:port], encryption: options[:encryption])
324
+ Entitlements.logger.debug "Binding with user #{binddn.inspect} with simple password authentication"
325
+ begin
326
+ result = connection.bind(options[:auth])
327
+ rescue StandardError
328
+ close_connection(connection)
329
+ raise
330
+ end
331
+ unless result.result_code == Net::LDAP::ResultCodeSuccess
332
+ close_connection(connection)
333
+ message = Net::LDAP.result2string(result.result_code)
334
+ Entitlements.logger.fatal message
335
+ raise ConnectionError, "FATAL: #{message}"
336
+ end
337
+ Entitlements.logger.debug "Successfully authenticated to #{options[:host]} port #{options[:port]}"
338
+
339
+ ldap_object = Net::LDAP.new(options)
340
+ ldap_object.connection = connection
341
+ @search_connections_mutex.synchronize { @search_connections[Thread.current] = [ldap_object, connection] }
342
+ ldap_object
343
+ end
344
+
345
+ def close_search_connection
346
+ _, connection = @search_connections_mutex.synchronize { @search_connections.delete(Thread.current) }
347
+ close_connection(connection)
348
+ end
349
+
350
+ def close_connection(connection)
351
+ connection&.close
352
+ rescue IOError, SystemCallError, OpenSSL::SSL::SSLError
353
+ nil
354
+ end
355
+
300
356
  def with_ldap_connection_retries
301
357
  attempts = 0
302
358
  begin
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Entitlements
4
4
  module Version
5
- VERSION = "1.2.11"
5
+ VERSION = "1.2.12"
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.11
4
+ version: 1.2.12
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-11-06 00:00:00.000000000 Z
11
+ date: 2026-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby