Authentication and authorization are two important aspects of any web application. Users should be able to log into the application (this is where the authentication part kicks in) and then view / update / create resources based on their access rights (this is for the authorization).
There are plenty of gems allowing to implement (more or less) quickly those two concepts in a Rails application. A lot of developments have happened in this field in the last few years and to get an overview of the different tools available, the easiest is probably to look at The Ruby Toolbox in the authentication and authorization sections.
Some time ago, most of the authorization gems allowed to define global roles in an application. This means that a user could be assigned an admin role (for instance) that was valid on all components of the application. This type of behavior works well for a forum for instance – where users either have a role to manage the different posts or not – but this isn’t so convenient in an application where more granularity is needed. In a project base application, users could be assigned a manager role on some projects but a viewer role on others and therefore have local roles (or scoped roles). ACL9 was one of the first gem (as far as I know) to offer such behavior and allow to define granular roles on some resources. The remaining of this post focuses on ACL9 and more precisely on how to link / access resources based on the user role.
Introduction
For this article, let’s consider a simple blog application in which users might have different access rights on the different posts. In this case, posts might for instance accept the following roles:
- admin: the user has all possible rights on the post (create, read, update, delete, …)
- editor: the user is allowed to create, edit and update the post
- reviewer: the user is allowed to read and update the post
- reader: the user is allowed to read the post
Users can then be assigned any of those roles for each post instance. As one can imagine, being reviewer on one post, does not mean that a user should be reviewer on every single post. ACL9 allows to define such granularity easily through its has_role!()
method. For more information about how this is done, have a look at the documentation.
Models Overview
Based on the project page and the documentation, one can set up the following interactions between the different models that are defined.
The diagram illustrates clearly how the three components (subject, role and object) are linked together. In this case, the role is linked to the object directly (in the background, it is actually a polymorphic association so that one role table can handle more than one object) and a user is linked to a role through an enrollment table. What might not be obvious from this diagram, are actually two things:
- How does one access the different Post objects on which the user has some rights (with an elegant
my_user.posts
for instance)? - How to identify the different users that have a specific role on a given object (through something like
my_post.editors
for instance)?
The next section will provide an answer to those questions.
Models Implementation
ACL9 wiki offers two ways to deal with the first question:
- Without using database views
- With database views
For a while, only the second solution was available. While this works well and is pretty straightforward to set up, it might be a pain later when one implements tests or wants to backup / restore the database. Indeed, by default views are not included in the DB schema.
The first option (i.e. without using database view) was introduced some time later by Enrique Vidal. However, while it sets the foundation right, the actual link between object and subject are not detailed in his solution. So let’s have a look at how to define these associations in the different models.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class User < ActiveRecord::Base
acts_as_authorization_subject :association_name => :roles, :join_table_name => :enrollments
has_many :enrollments
# Note: ACL9 adds automatically a `has_many :roles` associations.
# Trying to use this association instead of defining a new `post_roles` association to access the object
# (i.e. the post) might break other associations (if you have roles on other objects).
has_many :post_roles, :through => :enrollments, :source => :role
has_many :posts, :through => :post_roles, :source => :authorizable, :source_type => 'Post'
end
class Role < ActiveRecord::Base
acts_as_authorization_role :join_table_name => :enrollments
has_many :enrollments
has_many :users, :through => :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :user
belongs_to :role
validates :user, :role, :presence => true
end
class Post < ActiveRecord::Base
acts_as_authorization_object
has_many :user_roles, :as => :authorizable, :class_name => 'Role'
has_many :enrollments, :through => :user_roles
has_many :users, :source => :user, :through => :enrollments
has_many :admins, :source => :user, :through => :enrollments, :conditions => proc { "roles.name = 'admin'" }
has_many :editors, :source => :user, :through => :enrollments, :conditions => proc { "roles.name = 'editor'" }
has_many :reviewers, :source => :user, :through => :enrollments, :conditions => proc { "roles.name = 'reviewer'" }
has_many :readers, :source => :user, :through => :enrollments, :conditions => proc { "roles.name = 'reader'" }
end
Once this is in place, you will be able to access all posts on which a user has a roles with a “usual” my_user.posts
. On the other side, to get the list of users that are reviewers on a given post, a simple my_post.reviewers
will suffice.
Alternative Options
While it used to be complicated to implement local roles / scoped roles in order to provide different kinds of access to a given resource, the situation has evolved lately and a few other alternatives are now available. If you do not feel like using ACL9, you can give Rolify a try.
While I haven’t tested it extensively, it should interact well with Cancan to define scoped roles on resources.
For the time being, comments are managed by Disqus, a third-party library. I will eventually replace it with another solution, but the timeline is unclear. Considering the amount of data being loaded, if you would like to view comments or post a comment, click on the button below. For more information about why you see this button, take a look at the following article.