nathanl / authority

*CURRENTLY UNMAINTAINED*. Authority helps you authorize actions in your Rails app. It's ORM-neutral and has very little fancy syntax; just group your models under one or more Authorizer classes and write plain Ruby methods on them.
MIT License
1.21k stars 67 forks source link

defining authorisations with has_many / belongs_to association #37

Closed shlomizadok closed 11 years ago

shlomizadok commented 11 years ago

Sorry for all the noob questions and thank ya'll for helping I have nested models

class Orgunit < ActiveRecord::Base
  has_many :departments, :dependent => destroy
  ...
end

class Department < ActiveRecord::Base
  has_many :groups, :dependent => destroy
  belongs_to :orgunit
  ...
end

class Group < ActiveRecord::Base
  belongs_to :department
  ...
end

I am implementing Authority with instance authorisations. I use Rolify and grant authority per resource.

class InstanceAuthorizer < ApplicationAuthorizer
  def self.default(able, user)
    false
  end

  def updatable_by?(user)
    (user.has_role_for_resource? :admin, resource) || (user.has_role? :superadmin)
  end

the method has_role_for_resource? is defined in the user model:

def has_role_for_resource?(role_name, resource = nil)
  self.roles.detect { |r| r.name == role_name.to_s && (r.resource == resource) }.present?
end

I would like to give a role for a top resource (Orgunit) and that the user will be have authorisation for all it's child resources. Is there a quick and easy way to accomplish this?

nathanl commented 11 years ago

No problem about the questions. We're glad to help. :)

I don't know any standard way to do this. The concept of roles for parent resources might fall under Rolify's domain, but I think it's really too application-specific.

I see that the Rolify REAME gives examples like user.has_role? :moderator, Forum and user.has_role? :moderator, Forum.first. All you really need to add is a way to make that method check the parents of the model for roles, too.

Here's some untested pseudocode that might do that.

class OrgUnit
  def self.parent_resource; nil; end
  def parent_resource; nil; end

  def lineage
    item = self
    collection = [item]
    while item.respond_to?(:parent_resource) && item.parent_resource.present?
      collection << item.parent_resource
      item = item.parent_resource
    end
    collection
  end
end

class Department
  def self.parent_resource; OrgUnit; end
  def parent_resource; org_unit; end
end

class Group
  def self.parent_resource; Department; end
  def parent_resource; department; end
end

class User
  # Rolify defines this method in a module, so you should be able to override it and use `super`
  def has_role?(role_name, resource)
    resource.lineage.any? { |resource| super(role_name, resource) }
  end
end

Does that help?

shlomizadok commented 11 years ago

WOW. It helped a lot.

I ended up overriding has_role? with

def has_role?(role_name, resource=nil)
  if !resource.nil?
    resource.lineage.any? { |resource| has_role_for_resource?(role_name, resource) }
  else
    super
  end
end

  def has_role_for_resource?(role_name, resource = nil)
    self.roles.detect { |r| r.name == role_name.to_s && (r.resource == resource) }.present?
  end

(wanted to be more specific on user / role) Thank you so much!