arrow flex-icongithublightnings linkedinlock target triangle-icon twitter

Manage User Roles in Active Admin Using CanCan

Meritxell
August 03, 2018

At DVELP we like handy, easily-configurable and powerful tools, and the combination of Ruby on Rails and Active Admin fulfills these requirements.

Active Admin is a Ruby on Rails framework that provides an interface for resources administration. As you can see on their website, once we’ve configured the gem on our Rails server and added our resources, the interface looks like this:


Active admin interface

As shown in the schema above, Active Admin uses Devise for user authentication. Devise is composed of modules, which offer a lot of flexibility because we can choose only the ones that suit each case better.

Some modules are:

  • Database authenticatable: Will hash passwords before saving them to the database, and allow us to sign in using either POST or HTTP authentication.
  • Recoverable: The user can recover their password.
  • Rememberable: Will save the session for the user
  • Trackable: Will track sign in details.
  • Validatable: To validate email and passwords when creating a new account.

Active Admin and Devise together make up a really powerful combo. However, Devise only manages email, passwords and chronology of a user. It does not give us the opportunity to control what can each user do with each resource. This means that every user is allowed to create, edit and delete records. This is probably something we don’t want to happen in wild environments, therefore, we are missing a tool that allows us to determine what is the role for each user.

CanCan is an authorisation library which main feature is restricting how users can manage each resource. Let’s see how can we configure this gem to fit along with Active Admin and Devise:

First, add cancan gem to your Gemfile:

# Gemfile

gem 'cancan'

And run bundle install command.

Then, configure Active Admin to use CanCan as the authorization adapter:

# config/initializers/active_admin.rb

ActiveAdmin.setup do |config|
    config.authorization_adapter = ActiveAdmin::CanCanAdapter
    # Other configs
end

Now we’ll need to do a small trick to persist roles for each user: Add the column ‘role’ to admin_users table:

$ rails generate migration AddRoleToAdminUsers role:string

Then add restrictions in generated migration:

# db/migrate/<timestamp>_add_role_to_admin_users.rb

class AddRoleToAdminUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :admin_users,
      :role, :string,
      null: false,
      default: 'read'
  end
end

Don’t forget to run migrations after this step:

$ rails db:migrate

And now we’re heading to the core of this gem. “Ability” is the class where everything happens: This is where we determine what can each user do. For instance, if the user’s role is admin, it can manage all the resources. Otherwise, it will only be allowed to read the information. In the code:

# app/models/ability

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= AdminUser.new

    if user.role == 'admin'
      can :manage, :all
    else
      can :read, :all
    end
  end
end

To make this work, we will need to generate some users, assigning roles to them. In rails console:

AdminUser.create!(
    email: '[email protected]',
    password: 'password',
    password_confirmation: 'password',
    role: 'admin'
  )

  AdminUser.create!(
    email: '[email protected]',
    password: 'password',
    password_confirmation: 'password',
    role: 'read'
  )

Now you can manually test permissions for each user starting rails server and logging in using credentials for admin:


Admin user can create and delete records

Or read-only user:


Read-only user can’t create or delete records

Or you can use Capybara for automate testing:

# spec/features/login_page_spec.rb

it 'logs in registered admin users' do
    create(:admin_user, email: email)
    visit('http://localhost:3000/admin/login')

    fill_in 'admin_user_email', with: email
    fill_in 'admin_user_password', with: password
    click_on('Login')

    expect(page).to have_content('New Dial Plan')
end

it 'logs in registered readonly users' do
    create(:admin_user, :readonly, email: email)
    visit('http://localhost:3000/admin/login')

    fill_in 'admin_user_email', with: email
    fill_in 'admin_user_password', with: password
    click_on('Login')

    expect(page).to have_content('Dial Plans')
    expect(page).not_to have_content('New Dial Plan')
end

In the code above, I used FactoryBot to generate users:

# spec/factories/admin_users.rb

FactoryBot.define do
  factory :admin_user do
    sequence(:email) { |n| "test#{n}@example.com" }
    password 'a_password'
    password_confirmation 'a_password'
    role 'admin'

    trait :readonly do
      role 'read'
    end
  end
end

Although being a little tricky, implementing CanCan along with ActiveAdmin and Devise will allow us to manage user permissions. Have you ever faced this same problem? If so, let us know how you solved it!


For more information about CanCan integration, read this guide

Articles

By using this website you agree to our cookie policy
x