09 March 2013

Ruby on Rails Authentication and Authorization

This is an update to the source code to modify it for Rails 3.2 and Devise 2.2.3. I have also added some helpful bits to the default page and corrected flaws. You should at least already have Ruby, Ruby On Rails, Devise and CanCan installed. It would be preferable that the reader has gone through the five part article first, though not required.

The first difficulty that I found was the lack of a dedicated admin user. Our first goal will be to create the admin@nowhere.com user who has permissions to everything.

First lets create the following script which will encrypt our password for our admin user. Having already installed Devise the bcrypt should already be available.

password-encrypter.rb
require 'bcrypt'

pepper = nil
  cost = 10
  password='password'
  puts encrypted_password = ::BCrypt::Password.create("#{password}#{pepper}", :cost => cost).to_s

We should get something close to the following when running the script

Running password-encrypter.rb script
 c:filesarticle-update>ruby password-encrypter.rb
 $2a$10$BzIoyBkWO7iW2nZsdVn36eFSvvRrds/T5DjvVM.qnv79aL9lZIXve

This gives us the password so that we can use the SQLite Manager in Firefox to create our admin user. As shown below edit the record with an id of 1 and enter admin@nowhere.com for username/email and the encrypted password returned by your script for the password.

Here is the table …

User Table
User Table

Double click the record with id of 1 and edit it.

User Table Edit Record 1
User Table Edit Record 1

Next we need to use the same methods to create the “administrator” role in the role table.

Role Table
Role Table

Now we need to associate this role with our user by editing the user_role table and entering a user id of 1 and a role of id of 1 which we just edited and created.

User Role Table
User Role Table

Finally we go into the role_permissions table and add “all” for the controller and “manage” for the permissions.

Role Permissions Table
Role Permissions Table

We now have an admin user whom we have assigned management privileges to all controller actions. Now our journey can truly begin.

If you are like me you find it annoying to have to remember the URL addresses and type them into the application. Lets edit the static landing page for URL http://localhost:3000 so that it has links we can use.

public/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails: MySecurity Example</title>
    <style type="text/css" media="screen">
      body {
        margin: 0;
        margin-bottom: 25px;
        padding: 0;
        background-color: #f0f0f0;
        font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
        font-size: 13px;
        color: #333;
      }

      h1 {
        font-size: 28px;
        color: #000;
      }

      a  {color: #03c}
      a:hover {
        background-color: #03c;
        color: white;
        text-decoration: none;
      }


      #page {
        background-color: #f0f0f0;
        width: 750px;
        margin: 0;
        margin-left: auto;
        margin-right: auto;
      }

      #content {
        float: left;
        background-color: white;
        border: 3px solid #aaa;
        border-top: none;
        padding: 25px;
        width: 500px;
      }

      #sidebar {
        float: right;
        width: 175px;
      }

      #footer {
        clear: both;
      }

      #header, #about, #getting-started {
        padding-left: 75px;
        padding-right: 30px;
      }


      #header {
        background-image: url("/assets/rails.png");
        background-repeat: no-repeat;
        background-position: top left;
        height: 64px;
      }
      #header h1, #header h2 {margin: 0}
      #header h2 {
        color: #888;
        font-weight: normal;
        font-size: 16px;
      }


      #about h3 {
        margin: 0;
        margin-bottom: 10px;
        font-size: 14px;
      }

      #about-content {
        background-color: #ffd;
        border: 1px solid #fc0;
        margin-left: -55px;
        margin-right: -10px;
      }
      #about-content table {
        margin-top: 10px;
        margin-bottom: 10px;
        font-size: 11px;
        border-collapse: collapse;
      }
      #about-content td {
        padding: 10px;
        padding-top: 3px;
        padding-bottom: 3px;
      }
      #about-content td.name  {color: #555}
      #about-content td.value {color: #000}

      #about-content ul {
        padding: 0;
        list-style-type: none;
      }

      #about-content.failure {
        background-color: #fcc;
        border: 1px solid #f00;
      }
      #about-content.failure p {
        margin: 0;
        padding: 10px;
      }


      #getting-started {
        border-top: 1px solid #ccc;
        margin-top: 25px;
        padding-top: 15px;
      }
      #getting-started h1 {
        margin: 0;
        font-size: 20px;
      }
      #getting-started h2 {
        margin: 0;
        font-size: 14px;
        font-weight: normal;
        color: #333;
        margin-bottom: 25px;
      }
      #getting-started ol {
        margin-left: 0;
        padding-left: 0;
      }
      #getting-started li {
        font-size: 18px;
        color: #888;
        margin-bottom: 25px;
      }
      #getting-started li h2 {
        margin: 0;
        font-weight: normal;
        font-size: 18px;
        color: #333;
      }
      #getting-started li p {
        color: #555;
        font-size: 13px;
      }


      #sidebar ul {
        margin-left: 0;
        padding-left: 0;
      }
      #sidebar ul h3 {
        margin-top: 25px;
        font-size: 16px;
        padding-bottom: 10px;
        border-bottom: 1px solid #ccc;
      }
      #sidebar li {
        list-style-type: none;
      }
      #sidebar ul.links li {
        margin-bottom: 5px;
      }

      .filename {
        font-style: italic;
      }
    </style>
    <script type="text/javascript">
      function about() {
        info = document.getElementById('about-content');
        if (window.XMLHttpRequest)
          { xhr = new XMLHttpRequest(); }
        else
          { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
        xhr.open("GET","rails/info/properties",false);
        xhr.send("");
        info.innerHTML = xhr.responseText;
        info.style.display = 'block'
      }
    </script>
   <script src="/assets/jquery.js?body=1" type="text/javascript"></script>
   <script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
    
  </head>
  <body>
    <div id="page">
      <div id="sidebar">
        <ul id="sidebar-items">
          <li>
            <h3>Browse the documentation</h3>
            <ul class="links">
              <li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
              <li><a href="http://api.rubyonrails.org/">Rails API</a></li>
              <li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li>
              <li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li>
            </ul>
          </li>
        </ul>
      </div>

      <div id="content">
        <div id="header">
          <h1>Welcome aboard</h1>
          <h2>Ruby On Rails:  Mysecurity Example</h2>
        </div>

        <div id="about">
          <h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
          <div id="about-content" style="display: none"></div>
        </div>

        <div id="getting-started">
          <h1>Getting started</h1>
          <h2>Here&rsquo;s how to get rolling:</h2>

          <ol>
            <li>
              <h2>Read this article</h2>
              <p><a href='/2012/02/ruby-on-rails-authentication-and-authorization-part-1/'>Ruby on Rails Authentication and Authorization</a></p>
            </li>

            <li>
              <h2>Helpful Links</h2>
              <p>Log in - <a href="/user/sign_in">/user/sign_in</a></p>
              <p>Log out - <a href="/user/sign_out" data-method="delete" rel="nofollow">/user/sign_out</a></p>
              <p>Roles - <a href="roles">/roles</a></p>
              <p>Users - <a href="users">/users</a></p>
            </li>

          </ol>
        </div>
      </div>

      <div id="footer">&nbsp;</div>
    </div>
  </body>
</html>

Now we have a friendly landing that is a little helpful. Next lets update our gemfile to indicate rails 3.2 and to update our sass versions.

public/index.html
source 'http://rubygems.org'

gem 'rails', '3.2.11'

gem 'sqlite3'
gem 'devise'
gem 'cancan'


# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.6'
  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'

group :test do
  gem 'turn', '~> 0.8.3', :require => false
end

Now Rails application is configured but if you play around with it you will realize that the Role Permissions fail to load when on the Edit Role screen. This is because rails 3.2 handles helper files differently and it handles layouts differently.

Helper files are to be used with the View of the MVC model not with the controller which is what we chose to do the first time around. The contents of role_permissions_helper.rb should be copied and pasted to the end of the role_permissions_controller.rb file. The “layout nil” at the beginning of the controller no longer works to suppress the layout for a controller. Now instead we add “render :layout => nil if request.xhr?” to the “format.html” lines in the controller.

app/controllers/role_permissions_controller.rb
class RolePermissionsController < ApplicationController
  load_and_authorize_resource
  # GET /role_permissions
  # GET /role_permissions.json



  def index
    index_helper

    respond_to do |format|
      format.html { render :layout => nil if request.xhr? } # index.html.erb
      format.json { render json: @role_permissions }
    end
  end

  # GET /role_permissions/1
  # GET /role_permissions/1.json
  def show
    @role_permission = RolePermission.find(params[:id])
    #authorize! :show, @role_permission
    respond_to do |format|
      format.html { render :layout => nil if request.xhr? } # show.html.erb
      format.json { render json: @role_permission }
    end
  end

  # GET /role_permissions/new
  # GET /role_permissions/new.json



  def new
    prepare
    @role_permission = RolePermission.new
    @role_permission.role_id = params[:role_id]
    respond_to do |format|
      format.html { render :layout => nil if request.xhr? }# new.html.erb
      format.json { render json: @role_permission }
    end
  end

  # GET /role_permissions/1/edit
  def edit
    prepare
    @role_permission = RolePermission.find(params[:id])
    respond_to do |format|
      format.html { render :layout => nil if request.xhr? }# new.html.erb
      format.json { render json: @role_permission }
    end
  end

  # POST /role_permissions
  # POST /role_permissions.json
  def create
    @role_permission = RolePermission.new(params[:role_permission])
#    @role_permission = RolePermission.new()

    respond_to do |format|
      if @role_permission.save
        format.html { 
          index_helper
          render :action => 'index', :layout => nil if request.xhr?
        }
        format.json { render json: @role_permission, status: :created, location: @role_permission }
      else
        format.html { render action: "new", :layout => nil if request.xhr? }
        format.json { render json: @role_permission.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /role_permissions/1
  # PUT /role_permissions/1.json
  def update
    @role_permission = RolePermission.find(params[:id])

    respond_to do |format|
      if @role_permission.update_attributes(params[:role_permission])
        format.html {
          index_helper
          render :action => 'index', :layout => nil if request.xhr?
        }
        format.json { head :ok }
      else
        format.html { render action: "edit" }
        format.json { render json: @role_permission.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /role_permissions/1
  # DELETE /role_permissions/1.json
  def destroy
    @role_permission = RolePermission.find(params[:id])
    @role_permission.destroy

    respond_to do |format|
      format.html {
        index_helper
        render :action => 'index', :layout => nil if request.xhr?
      }
      format.json { head :ok }
    end
  end


  #private :prepare
  private
    def index_helper
    if params[:role_id].nil?
      if params[:role_permission][:role_id].nil?
        log_error " role_id parameter is invalid!"
      else
        @the_role_id = params[:role_permission][:role_id]
      end
    else
      @the_role_id = params[:role_id]
    end

    begin
      @role_permissions = RolePermission.find(:all,:conditions => ["role_id=?",@the_role_id])
    rescue ActiveRecord::RecordNotFound
      log_error "record not found role_id=" + params[:role_id]
    end
  end

  def prepare
    Rails.application.eager_load!
    @roles = Role.all
    @regulators = get_models
    @conducts = get_actions
  end

  def get_models
    regulators = Array.new
    ActiveRecord::Base.descendants.each{ |model|
      regulators[regulators.length] = model.name
    }
    return regulators
  end

  def get_actions
    conducts = Array.new
    ApplicationController.descendants.each { |regulator|
      the_actions = regulator.action_methods
      the_actions.each {|the_action|
        conducts[conducts.length] = the_action
      }
    }
    return conducts
  end

end

Additionally we need to go to appassetsjavascripts and delete the role_permissions.js.coffee file. With the controller updated and the file deleted we should now have a working role permissions controller.

Now comes the big finale! We modify the application_controller.rb to call Devise authentication. The application_controller is the parent of our other controllers and thus through the magic of inheritance all our controllers will that authentication.

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery
  before_filter :authenticate_user!

end

Finally make sure that your users controller and your role controller have the “load_and_authorize_resource” right after the class declaration.

app/controllers/users_controller.rb
class UsersController < ApplicationController
  load_and_authorize_resource
  #I cut out the rest for the sake of brevity.
end

Your Ruby on Rails 3.2 with authentication and authorization should be good to go! Happy programming.


Less Is More ~ Older posts are available in the archive.