Skip to content

Commit 4dac361

Browse files
committed
Use SpiceDB for lease-related auth checks
1 parent 6c62930 commit 4dac361

File tree

1 file changed

+134
-4
lines changed

1 file changed

+134
-4
lines changed

lib/declarative_authorization/authorization.rb

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,23 +186,153 @@ def permit!(privilege, options = {})
186186
attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
187187
rules = matching_auth_rules(roles, privileges, options[:context])
188188

189-
# Test each rule in turn to see whether any one of them is satisfied.
189+
# _____ _ ____ ____
190+
# / ___| (_) | _ \| _ \
191+
# \ `--. _ __ _ ___ ___| | | | |_) |
192+
# `--. \ '_ \| |/ __/ _ \ | | | _ <
193+
# /\__/ / |_) | | (_| __/ |_| | |_) |
194+
# \____/| .__/|_|\___\___|____/|____/
195+
# | |
196+
# |_|
197+
use_spicedb_auth = false
198+
190199
rules.each do |rule|
191-
return true if rule.validate?(attr_validator, options[:skip_attribute_test])
200+
unless rule.role.to_s.start_with?("leases__", "lease_renewals__")
201+
# Existing behavior for non-lease-related rules
202+
return true if rule.validate?(attr_validator, options[:skip_attribute_test])
203+
next
204+
end
205+
206+
use_spicedb_auth = true
207+
208+
auth_service_class = Rails.application.config.try(:spicedb_authorization_service)
209+
@auth_service = auth_service_class.new
210+
211+
vhost_id = Core::Company.guid if defined?(Core::Company)
212+
raise StandardError, "Vhost ID not found" if vhost_id.nil?
213+
214+
puts "\n==== Processing new rule ===="
215+
puts "Rule: #{rule.inspect}"
216+
puts "Role: #{rule.role}"
217+
218+
permission_to_check = rule.role.to_s.gsub("__", "_") + "_permission"
219+
puts "Permission to check: #{permission_to_check}"
220+
221+
if rule.attributes.empty?
222+
puts "Rule has no attributes, checking spicedb directly"
223+
224+
authorized = @auth_service.check_permission(
225+
resource: { type: "vhost", id: vhost_id },
226+
permission: permission_to_check
227+
)
228+
puts "Authorized? #{authorized}"
229+
230+
return true if authorized
231+
else
232+
puts "Rule has #{rule.attributes.count} attributes, examining them:"
233+
234+
rule.attributes.each_with_index do |attribute, index|
235+
puts "\n -- Attribute ##{index + 1}: #{attribute.inspect}"
236+
237+
if attribute.instance_variable_defined?('@conditions_hash')
238+
conditions = attribute.instance_variable_get('@conditions_hash')
239+
puts " Conditions hash: #{conditions.inspect}"
240+
else
241+
puts " !! No conditions_hash instance variable found, skipping"
242+
next
243+
end
244+
245+
next unless conditions.is_a?(Hash)
246+
247+
puts " Checking conditions against current values:"
248+
condition_matched = false
249+
250+
if conditions.key?(:granular_permissions)
251+
rule_requires = conditions[:granular_permissions][1]
252+
actual_value = options[:object]&.granular_permissions if options[:object].respond_to?(:granular_permissions)
253+
254+
puts " Granular permissions - Rule requires: #{rule_requires}, Actual: #{actual_value}"
255+
if rule_requires == actual_value
256+
condition_matched = true
257+
puts " ✓ Granular permissions condition matched!"
258+
else
259+
puts " ✗ Granular permissions condition did not match"
260+
end
261+
else
262+
puts " (No granular_permissions condition to check)"
263+
end
264+
265+
if conditions.key?(:is_renewal)
266+
rule_requires = conditions[:is_renewal][1]
267+
# If options[:object] has is_renewal, we'll use that value instead of obtaining it from SpiceDB.
268+
# This allows for Blue Moon leases and others to be checked without having to make a SpiceDB call for the value of is_renewal.
269+
if options[:object].respond_to?(:is_renewal)
270+
actual_value = options[:object].is_renewal
271+
else
272+
# Check if the lease document is a renewal. Ideally we implement a method in the auth service that is better
273+
# suited for this kind of check, but for the POC we'll just use lookup_subjects since it does what we need.
274+
puts " Checking SpiceDB for `renewal` relation on lease document uuid: #{options[:object]&.lease_document_uuid.to_s}"
275+
response = @auth_service.lookup_subjects(
276+
resource_type: "lease_document",
277+
resource_id: options[:object]&.lease_document_uuid.to_s,
278+
permission: "renewal",
279+
subject_type: "lease_document",
280+
)
281+
actual_value = response[:subject_ids].any?
282+
end
283+
284+
puts " Is renewal - Rule requires: #{rule_requires}, Actual: #{actual_value}"
285+
if rule_requires == actual_value
286+
condition_matched = true
287+
puts " ✓ Is_renewal condition matched!"
288+
else
289+
puts " ✗ Is_renewal condition did not match"
290+
end
291+
else
292+
puts " (No is_renewal condition to check)"
293+
end
294+
295+
if condition_matched
296+
# For now, we will assume each rule's conditions are OR'd together. This is not the case
297+
# for all rules, but it's the most common case and a good starting point.
298+
puts " At least one matching condition found, checking spicedb"
299+
300+
authorized = @auth_service.check_permission(
301+
resource: { type: "vhost", id: vhost_id },
302+
permission: permission_to_check
303+
)
304+
puts " Authorized? #{authorized}"
305+
306+
return true if authorized
307+
else
308+
puts " No matching conditions, skipping spicedb check for this attribute"
309+
end
310+
end
311+
end
192312
end
193313

314+
source_prefix = use_spicedb_auth ? "SpiceDB authorization failed. " : ""
315+
194316
if options[:bang]
195317
if rules.empty?
196318
raise NotAuthorized, "No matching rules found for #{privilege} for User with id #{user.try(:id)} " +
197319
"(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
198320
"context #{options[:context].inspect})."
199321
else
200-
raise AttributeAuthorizationError, "#{privilege} not allowed for User with id #{user.try(:id)} on #{(options[:object] || options[:context]).inspect}."
322+
raise AttributeAuthorizationError, "#{source_prefix}#{privilege} not allowed for User with id #{user.try(:id)} " +
323+
"on #{(options[:object] || options[:context]).inspect}."
201324
end
202325
else
203326
false
204327
end
205-
end
328+
end
329+
# _____ _ _ _____
330+
# | ___| | \ | | | _ |
331+
# | |__ | \| | | | | |
332+
# | __| | . ` | | | | |
333+
# | |___ | |\ | \ \_/ /
334+
# \____/ \_| \_/ \___/
335+
206336

207337
# Calls permit! but doesn't raise authorization errors. If no exception is
208338
# raised, permit? returns true and yields to the optional block.

0 commit comments

Comments
 (0)