authenticates_access
authenticates_access is a Rails plugin for doing model-based access control. It should really be called authorizes_access but the name seems to have stuck. Head on over to github and check it out.
Accessors
These are models which act as security “principals” - they request access to an object, and the object has rules and methods to determine whether the accessor should be allowed a certain type of access. Examples of possible accessors include users, groups, or API keys. They needn't be derived from ActiveRecord and could be a custom class.
Installing
ruby script/plugin install git://github.com/asquared/authenticates_access.git
Controller Setup
In your ApplicationController, load up an accessor before actions take place:
class ApplicationController < ActionController::Base
...
before_filter :set_up_accessor
...
protected
def set_up_accessor
ActiveRecord::Base.accessor = yourAccessor
end
end
Model Setup
In your models, set up some access rules using class methods. The rules of precedence are as follows.
- Rules of the same type are evaluated in the order they appear in the model.
- If any rule in a list passes, access is allowed. All rules must fail for access to be denied.
- If no rules of a given type are defined, access is always allowed.
- Writes to attributes are automatically denied if saves are being denied for the current accessor.
- Be aware, there is some magic for the predefined allow_owner test for a new record. See below.
authenticates_access - defines some magical instance methods for views and controllers to determine what is currently allowed. These include allowed_to_save, allowed_to_destroy, and allowed_to_write(attr). This is automatically done by any of the other methods so it is unlikely you need to do this explicitly.
authenticates_saves :with_accessor_method => :is_admin - allows the record to be saved (or destroyed) if the current accessor is an admin.
has_owner :user - following belongs_to :user, declares that the user is an owner of the object. Creates instance methods owner, owner_id, owner_id=, allow_owner.
authenticates_saves :with => :allow_owner - allows the record to be saved by its owner. This will also allow new records to be saved anonymously. Note that allow_owner is an instance method that will be added by has_owner.
authenticates_creation :with_accessor_method => :is_registered - restricts creation to only valid accessors for which is_registered returns true. Adds class method allowed_to_create.
autosets_owner_on_create - sets the owner ID to the accessor's ID, if the accessor is present. Be careful that the accessor is always of the same type as the owner!
authenticates_writes_to :something, :with_accessor_method => :is_admin - allows writes to the something attribute only if the accessor's is_admin method returns true.
Here's an example model from the RPI Electronics Club website (which is still under development:
class Comment < ActiveRecord::Base
belongs_to :member
belongs_to :meeting
belongs_to :photo
has_owner :member
autosets_owner_on_create
# this will allow a newly created object to be saved anonymously, since we
# don't authenticate_creation. But updates will be disallowed.
authenticates_saves :with => :allow_owner
authenticates_saves :with_accessor_method => :is_admin
authenticates_saves :with_accessor_method => :is_officer
authenticates_writes_to :member_id, :with_accessor_method => :is_admin
sanitizes_html :body, :allowed_tags => [ 'p', 'em', 'strong', 'span '], :safe_attributes => { 'span' => [ 'style' ] }
validates_presence_of :subject
validates_presence_of :body
...
end
Views
The instance and class methods defined by the different tests can be used to hide otherwise-unusable links in views. Here are a few examples from the electronics club website:
<% if OpenLab.allowed_to_create %>
<%= link_to 'Add lab hours', new_open_lab_path %>
<% end %>
<td>
<% if open_lab.allowed_to_save %>
<%= link_to 'Edit', edit_open_lab_path(open_lab) %>
<% end %>
</td>
<td>
<% if open_lab.allowed_to_destroy %>
<%= link_to 'Remove', open_lab, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
</td>
<% if @open_lab.allowed_to_write(:member_id) %>
<p>
<%= f.label :member_id %><br />
<%= f.collection_select(:member_id, Member.find(:all), :id, :name) %>
</p>
<% end %>
Error Handling
Saves will fail if the access tests don't pass, but currently, there's no message added to the record if it does. If you protect your links and form fields with the allowed_to_ methods, it shouldn't be an issue. This should be addressed in an upcoming release.