Implementing Roles and Permissions in Ruby on Rails

A common requirement of web applications is the ability to specify roles and permissions.

For example, many types of web application will have a distinction between admins and regular users. This can often be dealt with as a simple boolean on the user record.

But the granularity of roles and permissions can be much greater than this.

If access control is important to your application, it’s not something you want to mess up.

Your users are relying on you to restrict access to certain data and actions because that is where the value of your application lies.

In today’s tutorial we will be looking at implementing roles and permissions in a typical Ruby on Rails application.

The scenario we will be looking at

As I mentioned in the introduction to this post, there are a few different ways of dealing with roles and permissions in a web application.

For this application I’m going to be building a way for a user to have many roles within the application.

Each role will have slightly different permissions for the various resources of the application.

For example, an admin would be able to do everything, a member will have restricted access, and a guest will have read only access.

With that mapped out, lets take a look at what we need to build.

Adding the Role model

First we need to create the Role model that will for representing a role within the application.

The first thing we can do is to use the Rails generator to create the model

bin/rails g model Role name:string  
[/bash]

As you can see, this is a fairly simple model as a role only needs to have a name.

A role should always have a name and the name should always be unique, so we can add the following validations:  
```ruby  
class Role < ActiveRecord::Base  
validates :name, presence: true, uniqueness: true  
end  

Finally we can add some simple tests to assert that this is working as it should:

class RoleTest < ActiveSupport::TestCase  
should validate_presence_of(:name)  
should validate_uniqueness_of(:name)  
end  

Creating the Assignment model

Next we need to create the Assignment model that will associate a Role to a User. This is your classic “has many through” relationship.

Once again we can use the Rails generator to generate the model

bin/rails g model Assignment user:references role:references  
[/bash]

Running this command should automatically generate the following `Assignment` model:  
```ruby  
class Assignment < ActiveRecord::Base  
belongs_to :user  
belongs_to :role  
end  

We can add the following tests to the AssignmentTest class that was generated:

class AssignmentTest < ActiveSupport::TestCase  
should belong_to(:user)  
should belong_to(:role)  
end  

Next we can implement the relationships in the User and Role models and tests.

The User model will look like this:

class User < ActiveRecord::Base  
has_secure_password

has_many :assignments  
has_many :roles, through: :assignments  
end  

And we can add the following tests:

class UserTest < ActiveSupport::TestCase  
should have_many(:assignments)  
should have_many(:roles).through(:assignments)  
end  

And the Role model should look like this:

class Role < ActiveRecord::Base  
has_many :assignments  
has_many :users, through: :assignments

validates :name, presence: true, uniqueness: true  
end  

And we can add the following tests:

class RoleTest < ActiveSupport::TestCase  
should have_many(:assignments)  
should have_many(:users).through(:assignments)

should validate_presence_of(:name)  
should validate_uniqueness_of(:name)  
end  

Finally we can add a role? method to the User class for checking to see if the user has a particular role:

def role?(role)  
roles.any? { |r| r.name.underscore.to_sym == role }  
end  

Here I’m checking to see if the given role matches any of the user’s roles.

Here is the test to assert that this is working as it should:

test "user should have role" do  
assert_not(@subject.role? :admin)

@subject.roles << Role.new(name: "admin")

assert(@subject.role? :admin)  
end  

Adding Pundit

Now that we our models set up for users, roles, and assignments, we need a way of defining “policies” for determining what should happen when a user tries to access a given resource.

For example, we will have a Article resource, and so we need to have a matching ArticlePolicy to determine what should happen when a user tries to perform an action on that resource.

Instead of reinventing the wheel, we can use a tried and tested Open Source gem called Pundit.

Pundit allows you to define and enforce policies for your resources using simple Ruby objects.

To install Pundit, add the following line to your Gemfile:

gem "pundit"  

And then run the following command in Terminal:

bundle install  
[/bash]

Finally you can run the following command to generate the base policy:  
```bash  
bin/rails g pundit:install  
[/bash]

This will create a new directory under `app` called `policies` where you can store your Policies.


## Writing Policies

With Pundit installed, we can now start defining the policies for the application:  
```ruby  
class ArticlePolicy < ApplicationPolicy  
def update?  
user.role? :admin or not record.published?  
end  
end  

Each Policy is instantiated with an instance of the current user and the resource that we’re checking against.

By inheriting from the ApplicationPolicy we can skip the boiler plate. The resource object is named record by default.

You can define the rules for each action of that resource. For example, here I’m only allowing admins to update articles if they have already been published

Now you can prevent users from taking certain actions or from being able to access certain data based upon their role. I’ll not go through using Pundit in your Controllers or Views as there is already a lot of documentation on using the gem on the Pundit Github page.

Conclusion

Roles and permissions is an important concept in a wide variety of web applications.

All most all business oriented applications will have some sort of roles and permissions requirements. But all applications are slightly different and so there is no one sized fits all solution.

In today’s tutorial we have looked at how to add the roles and assignment models to assign roles to a user.

We have also looked at using Pundit, a gem that allows us to define policies and scopes for accessing the resources of the application.

Pundit allows you to group the access control rules in central policy objects so that your business logic is easy to find, and evolve over time.

Pundit is also just plain old Ruby code without any magic, so you can be rest assured that your user’s permissions will be enforced correctly.