entitlements-app 1.2.2 → 1.2.4

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: 18a0176e1783a420da17eee12174e9cc6f17224f980e991149743bd2366a1dbc
4
- data.tar.gz: 2acf3be9e885ade8181e3fb47adfc87fbfca609bb9978ca84c9d3b6816ae0ad5
3
+ metadata.gz: 2ff39c49f02bf4b3dcc8ced68357e2c85084416468d8300a79dd19c5685cac35
4
+ data.tar.gz: c40fb54374427dda1782344df0bdeb9d5401774a755e4c89d5795438d43a6605
5
5
  SHA512:
6
- metadata.gz: 73162232f10dca6c007c7ca5025a385d10ff06b809b1a0daace0fcc3dbcba877337b5a191963d173eeb64ff6635a4dd05f05857fb9ed37201f6cb5dfdd541ef9
7
- data.tar.gz: ef9e75e2916d78607a4f963f9661c1b36c48055b97d34f0f6286c2f6ba22324f8241d7775a15f37df4bc2a9ed1d823cba5b6553fd9db6e5c9e5050e1cc1fc0a0
6
+ metadata.gz: 0f0b5e0b20fc9a52547233b9b9f5dec204976d5a62d893a5d74b913af3222972fddadc12b2da5be4b7fb59ac8cf4722adc5f0da8a0415d8166cae3fc2066c119
7
+ data.tar.gz: 3585576afa5953596609cec10ca8995166e8803666cdece35003839dd365389eb46ee6e1298e8599537560083b28212f05e2d71155cd2dcc4e776f3ccbe934ec
@@ -217,7 +217,7 @@ module Entitlements
217
217
  return false if expiration.nil? || expiration.strip.empty?
218
218
  if expiration =~ /\A(\d{4})-(\d{2})-(\d{2})\z/
219
219
  year, month, day = Regexp.last_match(1).to_i, Regexp.last_match(2).to_i, Regexp.last_match(3).to_i
220
- return Time.utc(year, month, day, 0, 0, 0) <= Time.now.utc
220
+ return Time.utc(year, month, day, 0, 0, 0) <= Entitlements.evaluation_time.utc
221
221
  end
222
222
  message = "Invalid expiration date #{expiration.inspect} in #{context} (expected format: YYYY-MM-DD)"
223
223
  raise ArgumentError, message
@@ -340,7 +340,7 @@ module Entitlements
340
340
  # Returns C::SetOf[Entitlements::Models::Person] from a recursive call.
341
341
  def handle_and(rule)
342
342
  ensure_type!("and", rule, Array)
343
- return result unless rule.any?
343
+ return Set.new unless rule.any?
344
344
 
345
345
  first_rule = rule.shift
346
346
  ensure_type!("and", first_rule, Hash)
@@ -30,7 +30,7 @@ module Entitlements
30
30
  end
31
31
 
32
32
  # If the date is in the future, leave the entitlement unchanged.
33
- return false if parse_date > Time.now.utc.to_date
33
+ return false if parse_date > Entitlements.evaluation_time.utc.to_date
34
34
 
35
35
  # Empty the group. Set metadata allowing no members. Return true to indicate modification.
36
36
  rs.metadata["no_members_ok"] = true
@@ -21,10 +21,12 @@ module Entitlements
21
21
  # Returns a Set[String] with DN's of the people in the group.
22
22
  Contract C::None => C::Or[:calculating, C::SetOf[Entitlements::Models::Person]]
23
23
  def members
24
- @members ||= begin
25
- Entitlements.logger.debug "Calculating members from #{filename}"
26
- members_from_rules(rules)
27
- end
24
+ return @members if @members
25
+
26
+ Entitlements.logger.debug "Calculating members from #{filename}"
27
+ result = members_from_rules(rules)
28
+ @members = result unless result == :calculating
29
+ result
28
30
  end
29
31
 
30
32
  # Standard interface: Get the description of this group.
@@ -182,7 +184,7 @@ module Entitlements
182
184
  if parsed_data.key?("modifier_expiration") && affirmative.empty?
183
185
  exp_date = parsed_data.fetch("modifier_expiration").fetch("=").first.fetch(:key)
184
186
  date = Entitlements::Util::Util.parse_date(exp_date)
185
- return {"always" => false} if date <= Time.now.utc.to_date
187
+ return {"always" => false} if date <= Entitlements.evaluation_time.utc.to_date
186
188
  end
187
189
 
188
190
  # There has to be at least one affirmative condition, not just all negative ones.
@@ -18,10 +18,12 @@ module Entitlements
18
18
  # Returns a Set[String] with DN's of the people in the group.
19
19
  Contract C::None => C::Or[:calculating, C::SetOf[Entitlements::Models::Person]]
20
20
  def members
21
- @members ||= begin
22
- Entitlements.logger.debug "Calculating members from #{filename}"
23
- members_from_rules(rules)
24
- end
21
+ return @members if @members
22
+
23
+ Entitlements.logger.debug "Calculating members from #{filename}"
24
+ result = members_from_rules(rules)
25
+ @members = result unless result == :calculating
26
+ result
25
27
  end
26
28
 
27
29
  # Standard interface: Get the description of this group.
@@ -17,9 +17,9 @@ module Entitlements
17
17
  # This keeps the schema happy.
18
18
  attr_reader :binddn, :person_dn_format
19
19
 
20
- # Constructor-like object that ensures only one LDAP object (and hence connection)
21
- # is made for a given LDAP server, for efficiency sake. Takes the same parameters as
22
- # the constructor and returns the same object type.
20
+ # Constructor-like object that ensures only one LDAP service object is made for a
21
+ # given LDAP server. Takes the same parameters as the constructor and returns the
22
+ # same object type.
23
23
  #
24
24
  # addr - URL of LDAP server e.g. ldaps://ldap.example.net:636
25
25
  # binddn - DN to bind with
@@ -80,6 +80,8 @@ module Entitlements
80
80
  @ca_file = ca_file
81
81
  @disable_ssl_verification = disable_ssl_verification
82
82
  @person_dn_format = person_dn_format
83
+ @known_existing_dns = {}
84
+ @known_existing_dns_mutex = Mutex.new
83
85
  end
84
86
 
85
87
  # Read a single entry identified by its DN and return the value. Returns nil if
@@ -119,7 +121,13 @@ module Entitlements
119
121
  downcased_attrs = attrs == "*" ? "*" : attrs.map { |a| a.downcase }
120
122
 
121
123
  result = {}
122
- ldap.search(base: base, filter: filter, attributes: downcased_attrs, scope: scope, return_result: false) do |entry|
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|
123
131
  result_key = index == :dn ? entry.dn : entry[index]
124
132
  unless result_key
125
133
  raise EntryError, "#{entry.dn} has no value for #{index.inspect}"
@@ -132,6 +140,7 @@ module Entitlements
132
140
 
133
141
  result[result_key] = entry
134
142
  end
143
+ remember_existing_dn(base) if search_succeeded
135
144
 
136
145
  Entitlements.logger.debug "Completed search: #{result.keys.size} result(s)"
137
146
 
@@ -145,6 +154,7 @@ module Entitlements
145
154
  # Returns true if the entry exists, false otherwise.
146
155
  Contract String => C::Bool
147
156
  def exists?(dn)
157
+ return true if known_existing_dn?(dn)
148
158
  read(dn).is_a?(Net::LDAP::Entry)
149
159
  end
150
160
 
@@ -180,7 +190,10 @@ module Entitlements
180
190
 
181
191
  ldap.delete(dn: dn)
182
192
  operation_result = ldap.get_operation_result
183
- return true if operation_result["code"] == 0
193
+ if operation_result["code"] == 0
194
+ forget_dn(dn)
195
+ return true
196
+ end
184
197
  Entitlements.logger.error "Error deleting #{dn}: #{operation_result['message']}"
185
198
  false
186
199
  end
@@ -215,11 +228,24 @@ module Entitlements
215
228
 
216
229
  attr_reader :addr, :bindpw
217
230
 
218
- # The LDAP object is initialized and bound on demand the first time it's called.
231
+ def known_existing_dn?(dn)
232
+ @known_existing_dns_mutex.synchronize { @known_existing_dns.key?(dn) }
233
+ end
234
+
235
+ def remember_existing_dn(dn)
236
+ @known_existing_dns_mutex.synchronize { @known_existing_dns[dn] = true }
237
+ end
238
+
239
+ def forget_dn(dn)
240
+ @known_existing_dns_mutex.synchronize { @known_existing_dns.delete(dn) }
241
+ @dn_cache&.delete(dn)
242
+ end
243
+
244
+ # The LDAP object is initialized and its credentials are validated on demand.
219
245
  #
220
246
  # Takes no arguments.
221
247
  #
222
- # Returns a Net::LDAP object that is connected and bound.
248
+ # Returns a Net::LDAP object configured to connect and bind for each operation.
223
249
  Contract C::None => Net::LDAP
224
250
  def ldap
225
251
  @ldap ||= begin
data/lib/entitlements.rb CHANGED
@@ -90,6 +90,7 @@ module Entitlements
90
90
  @config = nil
91
91
  @config_file = nil
92
92
  @config_path_override = nil
93
+ @evaluation_time = nil
93
94
  @person_extra_methods = {}
94
95
  @statsd = nil
95
96
 
@@ -97,6 +98,24 @@ module Entitlements
97
98
  Entitlements::Data::Groups::Calculated.reset!
98
99
  end
99
100
 
101
+ # Return the fixed time used for the current date-sensitive entitlement evaluation.
102
+ #
103
+ # Returns a Time.
104
+ Contract C::None => Time
105
+ def self.evaluation_time
106
+ @evaluation_time ||= Time.now
107
+ end
108
+
109
+ # Set the time used for date-sensitive entitlement evaluation.
110
+ #
111
+ # value - A Time.
112
+ #
113
+ # Returns the supplied Time.
114
+ Contract Time => Time
115
+ def self.evaluation_time=(value)
116
+ @evaluation_time = value
117
+ end
118
+
100
119
  def self.reset_extras!
101
120
  extras_loaded = @extras_loaded
102
121
  if extras_loaded
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Entitlements
4
4
  module Version
5
- VERSION = "1.2.2"
5
+ VERSION = "1.2.4"
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.2
4
+ version: 1.2.4
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-09-08 00:00:00.000000000 Z
11
+ date: 2026-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby