Module: Hobo::Model::Permissions

Defined in:
lib/hobo/model/permissions.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.find_aliased_name(klass, method_name) ⇒ Object



25
26
27
28
29
30
31
32
# File 'lib/hobo/model/permissions.rb', line 25

def self.find_aliased_name(klass, method_name)
  # The method +method_name+ will have been aliased. We jump through some hoops to figure out
  # what it's new name is
  method_name = method_name.to_sym
  method = klass.instance_method method_name
  methods = (klass.private_instance_methods + klass.instance_methods).*.to_sym
  new_name = methods.select {|m| klass.instance_method(m) == method }.find { |m| m != method_name }
end

.included(klass) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/hobo/model/permissions.rb', line 6

def self.included(klass)
  klass.class_eval do
    extend ClassMethods

    alias_method_chain :_create_record, :hobo_permission_check
    alias_method_chain :_update_record, :hobo_permission_check
    alias_method_chain :destroy, :hobo_permission_check
    class << self
      alias_method_chain :has_many, :hobo_permission_check
      alias_method_chain :has_one, :hobo_permission_check
      alias_method_chain :belongs_to, :hobo_permission_check
    end

    attr_accessor :acting_user, :origin, :origin_attribute

    bool_attr_accessor :exempt_from_edit_checks
  end
end

Instance Method Details

#_create_record_with_hobo_permission_check(*args, &b) ⇒ Object



135
136
137
138
139
140
# File 'lib/hobo/model/permissions.rb', line 135

def _create_record_with_hobo_permission_check(*args, &b)
  if permission_check_required?
    create_permitted? or raise PermissionDeniedError, "#{self.class.name} #{self.id}#create"
  end
  _create_record_without_hobo_permission_check(*args, &b)
end

#_update_record_with_hobo_permission_check(*args) ⇒ Object



142
143
144
145
146
147
# File 'lib/hobo/model/permissions.rb', line 142

def _update_record_with_hobo_permission_check(*args)
  if permission_check_required?
    update_permitted? or raise PermissionDeniedError, "#{self.class.name} #{self.id}#update"
  end
  _update_record_without_hobo_permission_check(*args)
end

#all_changed?(*attributes) ⇒ Boolean

Returns:



327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/hobo/model/permissions.rb', line 327

def all_changed?(*attributes)
  attributes = prepare_attributes_for_change_helpers(attributes)
  attributes.all? do |attr|
    with_attribute_or_belongs_to_keys(attr) do |a, ftype|
      if ftype
        changed.include?(a) || changed.include?(ftype)
      else
        changed.include?(a)
      end
    end
  end
end

#any_changed?(*attributes) ⇒ Boolean

Returns:



315
316
317
318
319
320
321
322
323
324
325
# File 'lib/hobo/model/permissions.rb', line 315

def any_changed?(*attributes)
  attributes.any? do |attr|
    with_attribute_or_belongs_to_keys(attr) do |a, ftype|
      if ftype
        changed.include?(a) || changed.include?(ftype)
      else
        changed.include?(a)
      end
    end
  end
end

#association_editable_by?(user, reflection) ⇒ Boolean

Returns:



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/hobo/model/permissions.rb', line 276

def association_editable_by?(user, reflection)
  # has_one and polymorphic associations are not editable (for now)
  return false if reflection.macro == :has_one || reflection.options[:polymorphic]

  return false unless reflection.options[:accessible]

  record = if (through = reflection.through_reflection)
             # For edit permission on a has_many :through,
             # the user needs create+destroy permission on the join model
             send(through.name).new_candidate
           else
             # For edit permission on a regular has_many,
             # the user needs create/destroy permission on the member model
             send(reflection.name).new_candidate
           end
  record.creatable_by?(user) && record.destroyable_by?(user)
end

#attribute_protected?(attribute) ⇒ Boolean

Returns:



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/hobo/model/permissions.rb', line 257

def attribute_protected?(attribute)
  return false if attribute.nil?
  attribute = attribute.to_s

  return true if self.class.send(:attributes_protected_by_default).include? attribute

  if !self.class.accessible_attributes.empty?
    return true if !self.class.accessible_attributes.include?(attribute)
  elsif self.class.protected_attributes
    return true if self.class.protected_attributes.include?(attribute)
  end

  # Readonly attributes can be set on creation but not thereafter
  return self.class.readonly_attributes.include?(attribute) if !new_record? && self.class.readonly_attributes

  false
end

#creatable_by?(user) ⇒ Boolean

Returns:



202
203
204
# File 'lib/hobo/model/permissions.rb', line 202

def creatable_by?(user)
  with_acting_user(user) { create_permitted? }
end

#create_permitted?Boolean

Conservative default permissions #

Returns:



360
# File 'lib/hobo/model/permissions.rb', line 360

def create_permitted?;  false end

#destroy_permitted?Boolean

Returns:



362
# File 'lib/hobo/model/permissions.rb', line 362

def destroy_permitted?; false end

#destroy_with_hobo_permission_checkObject



149
150
151
152
153
154
155
# File 'lib/hobo/model/permissions.rb', line 149

def destroy_with_hobo_permission_check
  if permission_check_required?
    destroy_permitted? or raise PermissionDeniedError, "#{self.class.name} #{self.id}#.destroy"
  end

  destroy_without_hobo_permission_check
end

#destroyable_by?(user) ⇒ Boolean

Returns:



210
211
212
# File 'lib/hobo/model/permissions.rb', line 210

def destroyable_by?(user)
  with_acting_user(user) { destroy_permitted? }
end

#deunknownify_attribute(attr, remove_globals = true) ⇒ Object

Best. Name. Ever



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/hobo/model/permissions.rb', line 427

def deunknownify_attribute(attr, remove_globals = true)
  attr = attr.to_sym

  metaclass.send :remove_method, attr

  if (refl = self.class.reflections[attr.to_s]) && refl.macro == :belongs_to
    # A belongs_to -- restore the underlying fields
    deunknownify_attribute refl.foreign_key
    deunknownify_attribute(refl.options[:foreign_type], false) if refl.options[:polymorphic]
  else
    # A regular field -- restore the dirty tracking methods
    # if remove_globals is false, skip the top-level methods, as we have already removed them
    to_remove = remove_globals ? [:changed?, :changed, :changes] : []
    (["#{attr}_change", "#{attr}_was", "#{attr}_changed?"] + to_remove).each do |m|
      metaclass.send :remove_method, m.to_sym
    end
  end
end

#edit_permitted?(attribute) ⇒ Boolean

By default, attempt to derive edit permission from create/update permission

Returns:



368
369
370
371
372
373
374
375
376
377
# File 'lib/hobo/model/permissions.rb', line 368

def edit_permitted?(attribute)
  unknownify_attribute(attribute) if attribute
  new_record? ? create_permitted? : update_permitted?
rescue Hobo::UndefinedAccessError
  # The permission is dependent on the unknown value
  # so this attribute is not editable
  false
ensure
  deunknownify_attribute(attribute) if attribute
end

#editable_by?(user, attribute = nil) ⇒ Boolean

Returns:



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/hobo/model/permissions.rb', line 228

def editable_by?(user, attribute=nil)
  return false if attribute_protected?(attribute)

  return true if exempt_from_edit_checks?

  # Can't view implies can't edit
  return false unless viewable_by?(user, attribute)

  if attribute
    attribute = attribute.to_s.sub(/\?$/, '').to_sym

    # Try the attribute-specific edit-permission method if there is one
    if has_hobo_method?(meth = "#{attribute}_edit_permitted?")
      return with_acting_user(user) { send(meth) }
    end

    # No setter = no edit permission
    return false if !respond_to?("#{attribute}=")

    refl = self.class.reflections[attribute.to_s]
    if refl && refl.macro != :belongs_to # a belongs_to is handled the same as a regular attribute
      return association_editable_by?(user, refl)
    end
  end

  with_acting_user(user) { edit_permitted?(attribute) }
end

#method_callable_by?(user, method) ⇒ Boolean

Returns:



214
215
216
217
# File 'lib/hobo/model/permissions.rb', line 214

def method_callable_by?(user, method)
  permission_method = "#{method}_permitted?"
  respond_to?(permission_method) && with_acting_user(user) { send(permission_method) }
end

#none_changed?(*attributes) ⇒ Boolean

Returns:



307
308
309
310
311
312
313
# File 'lib/hobo/model/permissions.rb', line 307

def none_changed?(*attributes)
  attributes = attributes.map do |attr|
    with_attribute_or_belongs_to_keys(attr) { |a, ftype| ftype ? [a, ftype] : a }
  end.flatten

  attributes.all? { |attr| !changed.include?(attr) }
end

#only_changed?(*attributes) ⇒ Boolean

— Permission Declaration Helpers — #

Returns:



299
300
301
302
303
304
305
# File 'lib/hobo/model/permissions.rb', line 299

def only_changed?(*attributes)
  attributes = attributes.map do |attr|
    with_attribute_or_belongs_to_keys(attr) { |a, ftype| ftype ? [a, ftype] : a }
  end.flatten

  changed.all? { |attr| attributes.include?(attr) }
end

#permission_check_required?Boolean

Returns:



130
131
132
133
# File 'lib/hobo/model/permissions.rb', line 130

def permission_check_required?
  # Lifecycle steps are exempt from permission checks
  acting_user && !(self.class.has_lifecycle? && lifecycle.active_step)
end

#unknownify_attribute(attr) ⇒ Object

Add some singleton methods to record to give the effect that attribute is unknown. That is, attempts to access the attribute will result in a Hobo::UndefinedAccessError



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/hobo/model/permissions.rb', line 382

def unknownify_attribute(attr)
  metaclass.class_eval do
    define_method attr do
      raise Hobo::UndefinedAccessError
    end
  end

  if (refl = self.class.reflections[attr.to_s]) && refl.macro == :belongs_to
    # A belongs_to -- also unknownify the underlying fields
    unknownify_attribute refl.foreign_key
    unknownify_attribute refl.options[:foreign_type] if refl.options[:polymorphic]
  else
    # A regular field -- hack the dirty tracking methods

    metaclass.class_eval do

      define_method "#{attr}_change" do
        raise Hobo::UndefinedAccessError
      end

      define_method "#{attr}_was" do
        read_attribute attr
      end

      define_method "#{attr}_changed?" do
        true
      end

      def changed?
        true
      end

      define_method :changed do
        changed_attributes.keys | [attr.to_s]
      end

      def changes
        raise Hobo::UndefinedAccessError
      end

    end
  end
end

#updatable_by?(user) ⇒ Boolean

Returns:



206
207
208
# File 'lib/hobo/model/permissions.rb', line 206

def updatable_by?(user)
  with_acting_user(user) { update_permitted? }
end

#update_permitted?Boolean

Returns:



361
# File 'lib/hobo/model/permissions.rb', line 361

def update_permitted?;  false end

#user_destroy(user) ⇒ Object



180
181
182
# File 'lib/hobo/model/permissions.rb', line 180

def user_destroy(user)
  with_acting_user(user) { destroy }
end

#user_save(user, validate = true) ⇒ Object



172
173
174
# File 'lib/hobo/model/permissions.rb', line 172

def user_save(user, validate = true)
  with_acting_user(user) { save(:validate => validate) }
end

#user_save!(user) ⇒ Object



176
177
178
# File 'lib/hobo/model/permissions.rb', line 176

def user_save!(user)
  with_acting_user(user) { save! }
end

#user_update_attributes(user, attributes) ⇒ Object



188
189
190
191
192
193
# File 'lib/hobo/model/permissions.rb', line 188

def user_update_attributes(user, attributes)
  with_acting_user(user) do
    self.attributes = attributes
    save
  end
end

#user_update_attributes!(user, attributes) ⇒ Object



195
196
197
198
199
200
# File 'lib/hobo/model/permissions.rb', line 195

def user_update_attributes!(user, attributes)
  with_acting_user(user) do
    self.attributes = attributes
    save!
  end
end

#user_view(user, attribute = nil) ⇒ Object



184
185
186
# File 'lib/hobo/model/permissions.rb', line 184

def user_view(user, attribute=nil)
  raise PermissionDeniedError unless viewable_by?(user, attribute)
end

#view_permitted?(attribute) ⇒ Boolean

Allow viewing by default

Returns:



365
# File 'lib/hobo/model/permissions.rb', line 365

def view_permitted?(attribute) true end

#viewable_by?(user, attribute = nil) ⇒ Boolean

Returns:



219
220
221
222
223
224
225
# File 'lib/hobo/model/permissions.rb', line 219

def viewable_by?(user, attribute=nil)
  if attribute
    attribute = attribute.to_s.sub(/\?$/, '').to_sym
    return false if attribute && self.class.never_show?(attribute)
  end
  with_acting_user(user) { view_permitted?(attribute) }
end

#with_acting_user(user) ⇒ Object



163
164
165
166
167
168
169
170
# File 'lib/hobo/model/permissions.rb', line 163

def with_acting_user(user)
  return yield if user == acting_user
  old = acting_user
  self.acting_user = user
  result = yield
  self.acting_user = old
  result
end

#with_attribute_or_belongs_to_keys(attribute) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
# File 'lib/hobo/model/permissions.rb', line 340

def with_attribute_or_belongs_to_keys(attribute)
  if (refl = self.class.reflections[attribute.to_s]) && refl.macro == :belongs_to
    if refl.options[:polymorphic]
      yield refl.foreign_key, refl.options[:foreign_type]
    else
      yield refl.foreign_key, nil
    end
  else
    yield attribute.to_s, nil
  end
end