module Hobo::Model::Permissions

Public Instance Methods

_create_record_with_hobo_permission_check(*args, &b) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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
changed?() click to toggle source
# File lib/hobo/model/permissions.rb, line 410
def changed?
  true
end
changes() click to toggle source
# File lib/hobo/model/permissions.rb, line 418
def changes
  raise Hobo::UndefinedAccessError
end
creatable_by?(user) click to toggle source
# File lib/hobo/model/permissions.rb, line 202
def creatable_by?(user)
  with_acting_user(user) { create_permitted? }
end
create_permitted?() click to toggle source

Conservative default permissions #

# File lib/hobo/model/permissions.rb, line 360
def create_permitted?;  false end
destroy_permitted?() click to toggle source
# File lib/hobo/model/permissions.rb, line 362
def destroy_permitted?; false end
destroy_with_hobo_permission_check() click to toggle source
# 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) click to toggle source
# 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) click to toggle source

Best. Name. Ever

# 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) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

— Permission Declaration Helpers — #

# 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?() click to toggle source

— Hook ActiveRecord CRUD actions — #

# 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) click to toggle source

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

# 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) click to toggle source
# File lib/hobo/model/permissions.rb, line 206
def updatable_by?(user)
  with_acting_user(user) { update_permitted? }
end
update_permitted?() click to toggle source
# File lib/hobo/model/permissions.rb, line 361
def update_permitted?;  false end
user_destroy(user) click to toggle source
# File lib/hobo/model/permissions.rb, line 180
def user_destroy(user)
  with_acting_user(user) { destroy }
end
user_save(user, validate = true) click to toggle source
# 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) click to toggle source
# File lib/hobo/model/permissions.rb, line 176
def user_save!(user)
  with_acting_user(user) { save! }
end
user_update_attributes(user, attributes) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

Allow viewing by default

# File lib/hobo/model/permissions.rb, line 365
def view_permitted?(attribute) true end
viewable_by?(user, attribute=nil) click to toggle source
# 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) { || ... } click to toggle source

Permissions API — #

# 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) { |foreign_key, options| ... } click to toggle source
# 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

Public Class Methods

find_aliased_name(klass, method_name) click to toggle source
# 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) click to toggle source
# 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