Project

General

Profile

« Previous | Next » 

Revision 54592c2f

Added by Dominic Cleal about 9 years ago

fixes #8817 - look up reports with all joins from host scoped_search

This changes the optimisation in d50c799 which caused errors for users with
host filters referencing tables other than hosts.

When retrieving all reports joined with authorised hosts, the nested joins need
to be passed into AR via .joins on the main scope (reports) rather than what
happened with scoped_search, which only specifies the joins on the inner scope.
In that case, they're ignored and not included in the table list.

Retrieving the conditionals and tables from scoped_search directly allows us to
build up a more correct authorisation AR query with joins.

View differences:

app/models/concerns/authorizable.rb
end
}
def self.authorized(permission = nil, resource = nil)
self.authorized_as(User.current, permission, resource)
end
# joins to another class, on which the authorization is applied
#
# permission can be nil (therefore we use Proc instead of lambda)
#
# e.g.
# Report.joins_authorized_as(user, Host, :view_hosts)
# Host.joins_authorized_as(user, Domain, :view_domains)
#
# Or you may simply use authorized for User.current
#
scope :joins_authorized_as, Proc.new { |user, resource, permission|
if user.nil?
self.where('1=0')
elsif user.admin?
self.scoped
else
Authorizer.new(user).find_collection(resource, :permission => permission, :joined_on => self)
end
}
def authorized?(permission)
return false if User.current.nil?
......
def allows_location_filtering?
allows_taxonomy_filtering?(:location_id)
end
def authorized(permission = nil, resource = nil)
authorized_as(User.current, permission, resource)
end
def joins_authorized(resource, permission = nil)
joins_authorized_as(User.current, resource, permission)
end
end
end
app/models/fact_value.rb
}
scope :my_facts, lambda {
unless User.current.admin? and Organization.current.nil? and Location.current.nil?
host_ids = Host.authorized(:view_hosts, Host).select('hosts.id').all
where(:fact_values => {:host_id => host_ids})
joins_authorized(Host, :view_hosts)
end
}
app/models/report.rb
# returns reports for hosts in the User's filter set
scope :my_reports, lambda {
if !User.current.admin? || Organization.expand(Organization.current).present? || Location.expand(Location.current).present?
where(:reports => {:host_id => Host.authorized(:view_hosts, Host)})
joins_authorized(Host, :view_hosts)
end
}
app/services/authorizer.rb
def find_collection(resource_class, options = {})
permission = options.delete :permission
# retrieve all filters relevant to this permission for the user
base = user.filters.joins(:permissions).where(["#{Permission.table_name}.resource_type = ?", resource_name(resource_class)])
all_filters = permission.nil? ? base : base.where(["#{Permission.table_name}.name = ?", permission])
......
*values]).uniq
all_filters = all_filters.all # load all records, so #empty? does not call extra COUNT(*) query
return resource_class.where('1=0') if all_filters.empty?
unless @base_collection.nil?
if @base_collection.empty?
return resource_class.where('1=0')
else
resource_class = resource_class.where(:id => base_ids)
# retrieve hash of scoping data parsed from filters (by scoped_search), e.g. where clauses, joins
scope_components = build_filtered_scope_components(resource_class, all_filters, options)
if options[:joined_on]
# build scope for the "joined_on" object filtered by the associated "resource_class"
assoc_name = options[:association_name]
assoc_name ||= options[:joined_on].reflect_on_all_associations.find { |a| a.klass.base_class == resource_class.base_class }.name
scope = options[:joined_on].joins(assoc_name => scope_components[:includes]).readonly(false)
# apply every where clause to the scope consecutively
scope_components[:where].inject(scope) do |scope_build,where|
where.is_a?(Hash) ? scope_build.where(resource_class.table_name => where) : scope_build.where(where)
end
else
# build regular filtered scope for "resource_class"
scope = resource_class.includes(scope_components[:includes]).joins(scope_components[:joins]).readonly(false)
scope_components[:where].inject(scope) { |scope_build,where| scope_build.where(where) }
end
end
return resource_class.scoped if all_filters.any?(&:unlimited?)
def build_filtered_scope_components(resource_class, all_filters, options)
result = { where: [], includes: [], joins: [] }
if all_filters.empty? || (!@base_collection.nil? && @base_collection.empty?)
result[:where] << '1=0'
return result
end
if @base_collection.present?
result[:where] << { id: base_ids }
end
return result if all_filters.any?(&:unlimited?)
search_string = build_scoped_search_condition(all_filters.select(&:limited?))
resource_class.search_for(search_string).readonly(false)
find_options = ScopedSearch::QueryBuilder.build_query(resource_class.scoped_search_definition, search_string, options)
result[:where] << find_options[:conditions]
result[:includes].push(*find_options[:include])
result[:joins].push(*find_options[:joins])
result
end
def build_scoped_search_condition(filters)
test/unit/authorizer_test.rb
assert_includes results, host
refute results.grep(host).first.readonly?
end
test "#find_collection(Host, :permission => :view_hosts, :joined_on: Report) for matching unlimited filter" do
permission = Permission.find_by_name('view_hosts')
FactoryGirl.create(:filter, :role => @role, :permissions => [permission], :unlimited => true)
host = FactoryGirl.create(:host)
report = FactoryGirl.create(:report, :host => host)
auth = Authorizer.new(@user)
assert_includes auth.find_collection(Host::Managed, :permission => :view_hosts, :joined_on => Report), report
end
test "#find_collection(Host, :permission => :view_hosts, :joined_on: Report) for matching limited filter" do
permission = Permission.find_by_name('view_hosts')
FactoryGirl.create(:filter, :role => @role, :permissions => [permission],
:search => 'hostgroup ~ hostgroup*')
host = FactoryGirl.create(:host, :with_hostgroup)
report = FactoryGirl.create(:report, :host => host)
auth = Authorizer.new(@user)
assert_includes auth.find_collection(Host::Managed, :permission => :view_hosts, :joined_on => Report), report
end
test "#find_collection(Host, :permission => :view_hosts, :joined_on: Report) for matching limited filter with base collection set" do
permission = Permission.find_by_name('view_hosts')
FactoryGirl.create(:filter, :role => @role, :permissions => [permission],
:search => 'hostgroup ~ hostgroup*')
(host1, host2) = FactoryGirl.create_pair(:host, :with_hostgroup)
report1 = FactoryGirl.create(:report, :host => host1)
report2 = FactoryGirl.create(:report, :host => host2)
auth = Authorizer.new(@user, :collection => [host2])
collection = auth.find_collection(Host::Managed, :permission => :view_hosts, :joined_on => Report)
refute_includes collection, report1
assert_includes collection, report2
end
end
test/unit/fact_value_test.rb
results = FactValue.search_for("host = #{host.fqdn}")
assert_empty results
end
end
describe '.my_facts' do
let(:target_host) { FactoryGirl.create(:host, :with_hostgroup, :with_facts) }
let(:other_host) { FactoryGirl.create(:host, :with_hostgroup, :with_facts) }
test 'returns all facts for admin' do
as_admin do
assert_empty (target_host.fact_values + other_host.fact_values).map(&:id) - FactValue.my_facts.map(&:id)
end
end
test 'returns visible facts for unlimited user' do
user_role = FactoryGirl.create(:user_user_role)
FactoryGirl.create(:filter, :role => user_role.role, :permissions => Permission.where(:name => 'view_hosts'), :unlimited => true)
as_user user_role.owner do
assert_empty (target_host.fact_values + other_host.fact_values).map(&:id) - FactValue.my_facts.map(&:id)
end
end
test 'returns visible facts for filtered user' do
user_role = FactoryGirl.create(:user_user_role)
FactoryGirl.create(:filter, :role => user_role.role, :permissions => Permission.where(:name => 'view_hosts'), :search => "hostgroup_id = #{target_host.hostgroup_id}")
as_user user_role.owner do
assert_equal target_host.fact_values.map(&:id).sort, FactValue.my_facts.map(&:id).sort
end
end
end
end
test/unit/report_test.rb
end
end
end
describe '.my_reports' do
setup do
@target_host = FactoryGirl.create(:host, :with_hostgroup)
@target_reports = FactoryGirl.create_pair(:report, host: @target_host)
@other_host = FactoryGirl.create(:host, :with_hostgroup)
@other_reports = FactoryGirl.create_pair(:report, host: @other_host)
end
test 'returns all reports for admin' do
as_admin do
assert_empty (@target_reports + @other_reports).map(&:id) - Report.my_reports.map(&:id)
end
end
test 'returns visible reports for unlimited user' do
user_role = FactoryGirl.create(:user_user_role)
FactoryGirl.create(:filter, :role => user_role.role, :permissions => Permission.where(:name => 'view_hosts'), :unlimited => true)
collection = as_user(user_role.owner) { Report.my_reports }
assert_empty (@target_reports + @other_reports).map(&:id) - collection.map(&:id)
end
test 'returns visible reports for filtered user' do
user_role = FactoryGirl.create(:user_user_role)
FactoryGirl.create(:filter, :role => user_role.role, :permissions => Permission.where(:name => 'view_hosts'), :search => "hostgroup_id = #{@target_host.hostgroup_id}")
as_user user_role.owner do
assert_equal @target_reports.map(&:id).sort, Report.my_reports.map(&:id).sort
end
end
end
end

Also available in: Unified diff