Ruby on Rails Authentication and Authorization Part 5
Created: 9 May 2012 Modified:
In Part 1 of this series we described what we wanted our
application to accomplish and installed gems we would need to work with. In
Part 2 we generate our application framework and integrate Devise
into it. In Part 3 we generate the MVC for roles and customize
them to allow for building hierarchies. In Part 4 we customize
Devise to integrate roles and users. This this part we will tie all of this together and emerge with a full blow authentication and
authorization framework for any future applications.
To paraphrase Clausewitz “No application design survives contact with reality.” The original design called for the use of
declarative_authorization but we are going to be switching to CanCan for authorization. The reason for this is that
declarative_authorization uses a static file to define roles and CanCan will allow us to define roles from a database. This will allow us
to build permissions during runtime.
Since we are no longer using declarative_authorization and are using CanCan the CanCan gem needs to be installed and the Gemfile updated
like so.
Terminal window in the mysecurity directory
mysecurity/Gemfile
Now that our Gemfile is in order we will want a table to store the permissions. The first step will be to design the table and update our
database diagram as shown below.
The design will allow us to assign controllers and actions to roles. Using Merriam-Webster’s thesaurus I used regulator for model and conduct for action. I did this to avoid any name conflicts that might occur in the Rails code. You might be confused because regulator is not a synonym for model. Before I understood CanCan I was going to use controller and action for permissions:) Our next step will be to generate the model as shown below.
Terminal window in the mysecurity directory
With our table in hand we can define the relationship between roles and role_permissions.
mysecurity/app/models/role.rb
What we will end up with is a Role edit page where the user can assign and remove permissions for a role. We will end up with something close to the screen below.
To accomplish this will require the modification and creation of several files. There were several pieces of advice on how to work with AJAX and rails in the world wide web. In the end the only method that worked well was to start from scratch using JQuery and figure it out. This is the path we will be going down today.
The first file we will modifiy is the application layout to include application JavaScript and CSS as well as controller specific JavaScript and CSS. This code follows.
mysecurity/app/views/layouts/application.html.erb
Our next move will be to modify the Roles edit page to pull in via AJAX the Role Permission pages. We need for the role_permissions index page to load by default and to take the role_id as a parameter. We also need the edit and show pages to load into the page seemlessly. The majority of the code to support AJAX will be in the allPages.js file we will create. The code that follows is needed to make this happen.
mysecurity/app/views/roles/edit.html.erb
The main purpose of allPages.js is abstract the functionality out of a specific page so that I can reuse it with other pages when opportunity
allows.
mysecurity/app/assets/javascripts/allPages.js
This code expects the role_permissions controller to be able to handle a role_id parameter. Additionally we want it to return to the index page after a permission is the subject of a CRUD operation. Normally I would perform a redirect after these operations to permit problems during a page refresh. Since these pages are being pulled in via AJAX that will not be needed and we can render the index page after each operation. This is the primary purpose of the index_helper method. This also contains helper methods to load a list of all models and actions into JavaScript which will be used in the Role Permissions edit page. At the top of the controller we set “layout nil” so that the layout files will not be loaded. These two files are shown below.
In the role_permissions controller you will see a reference to a log_error method which I defined in applicaiton_helper.rb as displayed below.
mysecurity/app/helpers/application_helper.rb
Now that our controllers are in order we can modify our views to work with them. The default page to be displayed is the Role Permissions
index and that is where we should start. The most important change here is that we have added CSS classes to the links so that the jQuery
code in allPages.js can find and bind to the links. We will perform the same on the new, show and edit pages as well. These four files
follow.
The next files to modify are the application.js and application.cs assets. Rails Assets are a subject all to themselves. Below are the modified files.
mysecurity/app/assets/javascripts/application.js
mysecurity/app/assets/stylesheets/application.css
As we can see the application.css file references a table.css file which needs to be added and is diplayed below.
mysecurity/app/assets/javascripts/application.js
Before proceeding further you should create one or two users. This can be done by going to http://localhost:3000/users/new . You can also do
this by going to http://localhost:3000/user/sign_up. I used “test@test.com” for my user name and “tester” for my password.
With our users created we are ready to get to the heart of the matter! CanCan relies on a model called Ability to determine whether a user
has permissions or not. This file can be generated as shown below.
Terminal window in the mysecurity directory
We will then modify mysecurity/app/models/ability.rb to determine the permissions assigned to the user. We will do this by looping through
the users roles and permissions.
mysecurity/app/models/ability.rb
The last part is execute the authorization check in a controller. Remember we haven’t assigned any permissions to our user yet. Modify
roles_controller.rb and change the show action to the following.
mysecurity/app/models/ability.rb
Start your application and login using the user account you created earlier. The results you should see is that you can see the index and edit pages but will receive an exception when you try to show a role. To automatically add the checks to all actions add “load_and_authorize_resource” to the top of the controller.
We now have a working authentication and authorization system. Since we are pulling the permissions from a database keep in mind that we have a chicken or an egg situation. We can’t use the interface to add permissions because we don’t have permissions yet. Your options are to remove the CanCan authorization checks long enough to add permissions or to go into the database directly and enter the necessary records.