Defining permissions with resources

awilliams edited this page May 17, 2011 · 4 revisions

Instead of defining permission actions one-by-one you can define permissions through resources. Permission resources work similiar to resources in the Rails router. Permission resources are very expressive and compact to write and can be used to protect your controllers automatically.

A resource can be defined like this:

class Permissions < Aegis::permissions

  role :user

  resources :notes do
    allow :user
  end

end

This generates the following actions for you:

user.may_show_note?(@note)
user.may_index_notes?
user.may_update_note?(@note)
user.may_create_note?
user.may_destroy_note?(@note)

You can control which of these five default actions are generated:

resources :notes, :only => [:index, :create]
resources :notes, :except => :destroy

Changing individual actions

Any allow or deny directive inside the resource affects all actions of that resource. You can configure individual actions like this:

resources :notes do
  allow :user
  action :create do
    deny :user
    allow :admin
  end
end

Adding custom actions

You can give a resource additional actions which are not CRUD verbs:

resources :apartments do
  action :rate
end

By default a resource action expects an object:

user.may_rate_apartment(@apartment)

If you want to define an action that refers to the entire resource as a collection, write this:

resources :apartments do
  action :map, :collection => true
end

A collection action takes no argument:

user.may_map_apartments?

Refering to objects

As seen above, some resource actions (create, update, destroy and show) expect the object to be checked as first argument. If you need to refer to this object in a permission block, use the implicit object. The following would allow only apartment owners to update their own apartments:

resources :apartments do
  action :update do
    allow :user do
      object.owner == user
    end
  end
end

Additional arguments are given as block arguments. Say you would like to parametrize a relocate action, so apartments may only be relocated within the same city:

resources :apartments do
  action :relocate do
    allow do |new_location|
      object.location.city == new_location.city
    end
  end
end

This permission would be checked like this:

apartment = Apartment.find(...)
new_location = Location.new(...)
@user.may_relocate_apartment?(apartment, new_location)

Singleton resources

By writing resource (singular) instead of resources, you can create a singleton resource:

resource :profile

Singleton resources take no object as argument and have no index action:

user.may_update_profile?

Nested resources

Resources can be nested:

resources :posts do

  resources :comments do

    action :update, :destroy do
      allow :admin
    end

    action :create, :index, :show do
      allow :everyone
    end

  end

end

This generates the following actions:

user.may_show_post_comment?(post, comment)
user.may_update_post_comment?(post, comment)
user.may_create_post_comment?(post)
user.may_destroy_post_comment?(post_comment)
user.may_index_post_comments?(post)

Nested resources have their own actions and allow/deny directives. They inherit nothing from their parent resources.

Accessing parent objects

Note how permission checks on nested resources take up to two arguments in the example above. The first argument is the parent object that provides the context inside the parent resource. If you need to refer to that object inside a permission block, write parent_object:

resources :posts do
  resources :comments do
    action :destroy do
      allow :admin do
        parent_object.author == user
      end
    end
  end
end

Namespacing resources

You can namespace your resources like this:

namespace :admin do
  resources :users
end

This generates the following actions:

user.may_show_admin_user?(@user)
user.may_index_admin_users?
user.may_update_admin_user?(@user)
user.may_create_admin_user?
user.may_destroy_admin_user?(@user)

Namespaces are useful to group resources together as your permissions grow. They only change the names of generated actions, but don’t alter behavior otherwise.

Some closing advice

Your permission resources do not necessarily need to be as complex and fine-grained as your routes/controllers. So don’t drive yourself crazy keeping those two in sync. It is more important to keep your permissions at a manageable size and complexity.