Skip to content
Simao Belchior de Castro edited this page Jun 2, 2014 · 3 revisions

We are using CanCan to manage authorization in our management websites. Our public websites allow anonymous access. In this page we will go into details into the way we are using CanCan.

Users and Access Rules

Our specifications define two type of users:

  • Managers: can manage all the data in the application, including Trade data;

and

  • Contributors: Can view, edit and add data to taxon concepts and associated pages. Contributors can not manage what is identified as Core Data (e.g.: Taxonomies, Ranks, Species Listings, etc), nor can they change data in bulk. They can not visit the Trade Database management page.

Implementation

Roles

As we only have two roles and each user can only have one of those roles, we decided to use only a boolean attribute in the User model. This attribute is named is_manager and it defaults to false for newly created users.

CanCan rules

We define the authorization rules in CanCan's ability file: app/models/ability.rb

Managers

can :manage, :all

Contributors

For Contributors we first grant access to the read, update, and create actions on all objects, leaving out the destroy action and thus preventing them from deleting any type of data:

can :read, :all
can :update, :all
can :create, :all

We then specify some exceptions to these rules.

Contributors can only update their own accounts.

cannot :update, User do |u|
  u.id != user.id
end

Contributors cannot manage Core Data, and do bulk updates (eg: admin_quotas_path, admin_eu_regulations_path)

cannot :manage, [
  Taxonomy, Rank, Designation,
  Instrument, SpeciesListing,
  ChangeType, EuDecisionType,
  Language, GeoEntity, GeoEntityType,
  TradeCode, Trade::TaxonConceptTermPair,
  TermTradeCodesPair, Event, CitesSuspension,
  Quota, EuRegulation, EuSuspensionRegulation,
  Trade::Shipment, Trade::Permit, Trade::AnnualReportUpload,
  Trade::ValidationRule
]

Enforcing authorization

Trade Admin

Access to the Trade Management page is controlled in app/controllers/trade_controller.rb, by calling the before filter verify_manager, which prevents non managers from accessing the trade namespace pages.

Admin

In the Species+ Admin the authorize_resource method is called in app/controllers/admin/standard_authorization_controller.rb which inherits from Admin::SimpleCrudController, and which then is used by most of the other controllers (e.g.: app/controllers/admin/taxonomies_controller.rb).

Some controllers that don't have a direct association with a Model are not inheriting from the StandardAuthorizationController, and call the authorization method like this:

authorize_resource :class => false

Otherwise a wrong name exception was being thrown, as CanCan was trying to get to a model with the name derived from the controller (e.g.: Admin::TaxonQuotasController => TaxonQuota, instead of Quota, as defined in the defaults method in the head of the controller file).

Another special case is the Admin::UsersController which has one rule:

cannot :update, User do |u|
  u.id != user.id
end

that needs to take into consideration the object being accessed by the edit method, and so instead of just calling authorize_resource the method load_and_authorize_resource needs to be called. As we are using the InheritedResources gem and are overwriting the collection method we need to pass in :except => :index into the load_and_authorize_resource method otherwise it doesn't work as per the CanCan documentation. As we allow contributors to view all other users, we don't need to add accessible_by to the collection method.

Authorizing links

To avoid confusion we hide some of the links from users depending on their role or on their level of access, like in app/views/shared/_topbar.html.erb

Handling exceptions

In app/controllers/application_controller.rb we added the following method:

rescue_from CanCan::AccessDenied do |exception|
   rescue_path = if request.referrer && request.referrer != request.url
             request.referer
           else
             admin_root_path
           end
   redirect_to rescue_path, :alert => case exception.action
     when :destroy
       "You are not authorized to destroy that record"
     else
       exception.message
     end
 end

Which will catch the AccessDenied exception and redirect the users either to the referrer page, if not the same, or to the Admin root page.