1# frozen_string_literal: true 2 3# Module to prepend into finders to specify whether or not the finder requires 4# cross project access 5# 6# This module depends on the finder implementing the following methods: 7# 8# - `#execute` should return an `ActiveRecord::Relation` or the `model` needs to 9# be defined in the call to `requires_cross_project_access`. 10# - `#current_user` the user that requires access (or nil) 11module FinderWithCrossProjectAccess 12 extend ActiveSupport::Concern 13 extend ::Gitlab::Utils::Override 14 15 prepended do 16 extend Gitlab::CrossProjectAccess::ClassMethods 17 18 cattr_accessor :finder_model 19 20 def self.requires_cross_project_access(*args) 21 super 22 23 self.finder_model = extract_model_from_arguments(args) 24 end 25 26 private 27 28 def self.extract_model_from_arguments(args) 29 args.detect { |argument| argument.is_a?(Hash) && argument[:model] } 30 &.fetch(:model) 31 end 32 end 33 34 override :execute 35 def execute(*args, **kwargs) 36 check = Gitlab::CrossProjectAccess.find_check(self) 37 original = -> { super } 38 39 return original.call unless check 40 return original.call if should_skip_cross_project_check || can_read_cross_project? 41 42 if check.should_run?(self) 43 finder_model&.none || original.call.model.none 44 else 45 original.call 46 end 47 end 48 49 # We can skip the cross project check for finding indivitual records. 50 # this would be handled by the `can?(:read_*, result)` call in `FinderMethods` 51 # itself. 52 override :find_by! 53 def find_by!(*args) 54 skip_cross_project_check { super } 55 end 56 57 override :find_by 58 def find_by(*args) 59 skip_cross_project_check { super } 60 end 61 62 override :find 63 def find(*args) 64 skip_cross_project_check { super } 65 end 66 67 attr_accessor :should_skip_cross_project_check 68 69 def skip_cross_project_check 70 self.should_skip_cross_project_check = true 71 72 yield 73 ensure 74 # The find could raise an `ActiveRecord::RecordNotFound`, after which we 75 # still want to re-enable the check. 76 self.should_skip_cross_project_check = false 77 end 78 79 def can_read_cross_project? 80 Ability.allowed?(current_user, :read_cross_project) 81 end 82 83 def can_read_project?(project) 84 Ability.allowed?(current_user, :read_project, project) 85 end 86end 87