Revision 54592c2f
Added by Dominic Cleal about 9 years ago
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
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.