-
Notifications
You must be signed in to change notification settings - Fork 982
/
filter.rb
240 lines (193 loc) · 7.57 KB
/
filter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
class Filter < ApplicationRecord
audited :associated_with => :role
include Taxonomix
include Authorizable
include TopbarCacheExpiry
attr_writer :resource_type
attr_accessor :unlimited
class ScopedSearchValidator < ActiveModel::Validator
def validate(record)
resource_class = record.resource_class
resource_class.search_for(record.search) unless (resource_class.nil? || record.search.nil?)
rescue ScopedSearch::Exception => e
record.errors.add(:search, _("invalid search query: %s") % e)
end
end
# tune up taxonomix for filters, we don't want to set current taxonomy
def add_current_organization?
false
end
def add_current_location?
false
end
# allow creating filters for non-taxable resources when user is not admin
def ensure_taxonomies_not_escalated
end
belongs_to :role
has_many :filterings, :autosave => true, :dependent => :destroy
has_many :permissions, :through => :filterings
validates_lengths_from_database
default_scope -> { order(["#{table_name}.role_id", "#{table_name}.id"]) }
scope :unlimited, -> { where(:search => nil, :taxonomy_search => nil) }
scope :limited, -> { where("search IS NOT NULL OR taxonomy_search IS NOT NULL") }
scoped_search :on => :id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
scoped_search :on => :search, :complete_value => true
scoped_search :on => :override, :complete_value => { :true => true, :false => false }
scoped_search :on => :limited, :complete_value => { :true => true, :false => false }, :ext_method => :search_by_limited, :only_explicit => true
scoped_search :on => :unlimited, :complete_value => { :true => true, :false => false }, :ext_method => :search_by_unlimited, :only_explicit => true
scoped_search :relation => :role, :on => :id, :rename => :role_id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
scoped_search :relation => :role, :on => :name, :rename => :role
scoped_search :relation => :permissions, :on => :resource_type, :rename => :resource
scoped_search :relation => :permissions, :on => :name, :rename => :permission
before_validation :build_taxonomy_search, :nilify_empty_searches, :enforce_override_flag
before_save :enforce_inherited_taxonomies, :nilify_empty_searches
validates :search, :presence => true, :unless => proc { |o| o.search.nil? }
validates_with ScopedSearchValidator
validates :role, :presence => true
validate :role_not_locked
before_destroy :role_not_locked
validate :same_resource_type_permissions, :not_empty_permissions, :allowed_taxonomies
def self.allows_taxonomy_filtering?(_taxonomy)
false
end
def self.search_by_unlimited(key, operator, value)
search_by_limited(key, operator, (value == 'true') ? 'false' : 'true')
end
def self.search_by_limited(key, operator, value)
value = value == 'true'
value = !value if operator == '<>'
conditions = value ? 'search IS NOT NULL OR taxonomy_search IS NOT NULL' : 'search IS NULL AND taxonomy_search IS NULL'
{ :conditions => conditions }
end
# This method attempts to return an existing class that is derived from the resource_type.
# In some instances, this may not be a real class (e.g. a typo) or may be nil in the case
# of a filter not having been saved yet and thus the permissions objects not being currently
# accessible.
def self.get_resource_class(resource_type)
return nil if resource_type.nil?
resource_type.constantize
rescue NameError => e
Foreman::Logging.exception("unknown class #{resource_type}, ignoring", e)
nil
end
def unlimited?
search.nil? && taxonomy_search.nil?
end
def limited?
!unlimited?
end
def to_s
_('filter for %s role') % role.try(:name) || 'unknown'
end
def to_label
permissions.pluck(:name).to_sentence
end
def resource_type
type = @resource_type || filterings.first.try(:permission).try(:resource_type)
type.presence
end
def resource_type_label
resource_class.try(:humanize_class_name) || resource_type || N_('(Miscellaneous)')
end
def resource_class
@resource_class ||= self.class.get_resource_class(resource_type)
end
# We detect granularity by inclusion of Authorizable module and scoped_search definition
# we can define exceptions for resources with more complex hierarchy (e.g. Host is proxy module)
def granular?
@granular ||= begin
return false if resource_class.nil?
return true if resource_type == 'Host'
resource_class.included_modules.include?(Authorizable) && resource_class.respond_to?(:search_for)
end
end
def resource_taxable?
resource_taxable_by_organization? || resource_taxable_by_location?
end
def resource_taxable_by_organization?
granular? && resource_class.allows_organization_filtering?
end
def resource_taxable_by_location?
granular? && resource_class.allows_location_filtering?
end
def search_condition
searches = [search]
searches << taxonomy_search
searches.compact!
searches.map! { |s| parenthesize(s) } if searches.size > 1
searches.join(' and ')
end
def expire_topbar_cache
role.users.each { |u| u.expire_topbar_cache }
role.usergroups.each { |g| g.expire_topbar_cache }
end
def disable_overriding!
self.override = false
save!
end
def enforce_inherited_taxonomies
inherit_taxonomies! unless override?
end
def inherit_taxonomies!
self.organization_ids = role.organization_ids if resource_taxable_by_organization?
self.location_ids = role.location_ids if resource_taxable_by_location?
build_taxonomy_search
end
private
def build_taxonomy_search
orgs = build_taxonomy_search_string('organization')
locs = build_taxonomy_search_string('location')
taxonomies = [orgs, locs].reject { |t| t.blank? }
self.taxonomy_search = taxonomies.join(' and ').presence
end
def build_taxonomy_search_string(name)
return '' unless send("resource_taxable_by_#{name}?")
relation = send(name.pluralize).pluck(:id)
return '' if relation.empty?
parenthesize("#{name}_id ^ (#{relation.join(',')})")
end
def nilify_empty_searches
self.search = nil if search.empty? || unlimited == '1'
self.taxonomy_search = nil if taxonomy_search.empty?
end
def parenthesize(string)
if string.blank? || (string.start_with?('(') && string.end_with?(')'))
string
else
"(#{string})"
end
end
# if we have 0 types, empty validation will set error, we can't have more than one type
def same_resource_type_permissions
types = permissions.map(&:resource_type).uniq
if types.size > 1
errors.add(
:permissions,
_('must be of same resource type (%{types}) - Role (%{role})') %
{
types: types.join(','),
role: role.name,
}
)
end
end
def not_empty_permissions
errors.add(:permissions, _('You must select at least one permission')) if permissions.blank? && filterings.blank?
end
def allowed_taxonomies
if organization_ids.present? && !resource_taxable_by_organization?
errors.add(:organization_ids, _('You can\'t assign organizations to this resource'))
end
if location_ids.present? && !resource_taxable_by_location?
errors.add(:location_ids, _('You can\'t assign locations to this resource'))
end
end
def enforce_override_flag
self.override = false unless resource_taxable?
true
end
def role_not_locked
errors.add(:role_id, _('is locked for user modifications.')) if role.locked? && !role.modify_locked
errors.empty?
end
end