For one of our current Ruby on Rails applications, we only had a simple login system, based on the acts_as_authenticated plugin.

However, there was a growing need to have some kind of (basic) permission system.

We wanted to keep it as lightweight as possible and came up with the following solution.

This tutorial starts from an existing project that is already using the acts_as_authenticated plugin for the user accounts.

What do we want to do ?
We want to be able to grant or deny access to a “project” for a specific user.
Of course, this also means that we need an “admin”-account to manage these permissions.

1. Create a new model / controller / views / migration :

user@your-pc:~/project_path$ruby script/generate rspec_scaffold permission user_id:integer project_id:integer

This will create everything at once: the model permission.rb, the controller permissions_controller.rb, a scaffold for the view and the RSpec tests that go along with it.
Your routes.rb file also be modified with the new RESTful routing entries for the permissions-section.
Ofcourse you can choose to do everything manually or to use different type of generators, the result will be pretty much the same.

Now, modify the generated migration to look something like this:

class CreatePermissions < ActiveRecord::Migration def self.up add_column :users, :admin, :boolean, :default => 0 if admin = User.find_by_login(“admin”) admin.update_attributes(:admin => 1) else user = User.new( :login => admin, :password => yourpass, :password_confirmation => yourpass, :email => “admin@address.com” ) user.save! end create_table :permissions do |t| t.column :user_id, :integer t.column :project_id, :integer end end def self.down drop_table :permissions remove_column :users, :admin end end

Execute the migration:

user@your-pc:~/project_path$ rake db:migrate

2. Set up the ActiveRecord relations

class Permissions < ActiveRecord::Base belongs_to :project belongs_to :user
  1. Prevent duplicate permissions
    validates_uniqueness_of :user_id, :scope => project_id,
    :message => “already has the permission for this project.”
    end
class Project < ActiveRecord::Base has_many :permissions, :dependent => :destroy
  1. if you already have a relation “has_many :users”, you’ll have to rename this one.
    has_many :users, :through => :permissions
    end
class User < ActiveRecord::Base has_many :permissions, :dependent => :destroy
  1. if you already have a relation “has_many :projects”, you’ll have to rename this one.
    has_many :projects, :through => :permissions
    end

3. Modify your controller / views.

Here I’m not going into detail, it’s just a matter of modifying the default scaffold layout according to your wishes.
The admin user will need a clean overview of the current permissions and an easy way to add and remove them.
Pretty basic stuff, but probably the part that will take the most of your time :-).

Now, let’s implement the admin restrictions.

class PermissionsController < ApplicationController before_filter :check_admin … … end class ApplicationController < ActionController::Base
  1. acts_as_authenticated plugin
    include AuthenticatedSystem
    before_filter :login_required, :except => [:login]

    def check_admin
    unless admin?
    flash[:error] = “You don’t have permission to access this page, please contact an Administrator.”
    redirect_to :controller => ‘projects’
    return
    end
    end
def admin? @admin ||= (current_user != :false ? current_user.admin : nil) end helper_method :admin? … end

That was cool ! With a few lines of code you’re preventing non-admin users from playing around with the permission-system.
In your view, you can now make a conditional link to the permissions page thanks to the “helper_method :admin” :

<% if admin? %> <%= link_to “Manage Permissions”, permissions_path, :class => “menuItem” %> <% end %>

Great! Next step: use our brand new permission system to prevent users from accessing projects that don’t belong to them.

4. Implementing the permission security on the projects

Your index action in the projects_controller might look something like this :

def index @projects = Project.find(:all) respond_to do |format| format.html # index.rhtml format.xml { render :xml => @projects.to_xml } end end

At http://…../projects, this gives you an overwiew of all projects in the database.

First, make sure you’ve assigned some projects to yourself in your newly designed permission interface.
Now change the first line to the following and watch what happens.

def index @projects = current_user.projects … end

WaW! Now you only get the projects on your screen to which you, the currently logged in user, has received permission to.

As an extra safety, you could add a before_filter to your projects_controller:

class ProjectsController < ApplicationController before_filter :check_permission, :except => [:index, :new, :create] … private def check_permission begin @project = current_user.projects.find(params[:id]) rescue #record not found flash[:error] = “This project ain’t none of your business !” redirect_to :controller => ‘projects’ return end end … end

I don’t think this last step is really necessary however…
If you make sure that you always use current_user.projects.find(…) instead of just Project.find() (also in your other controllers), you should be safe.

Todo

I didn’t implement the interface for the user-management yet (assigning admin rights to users), but that’s really easy from here, I’m sure you can manage that :-).

Alternative(s)

It might have been better to use a Rails plugin for this, a quick search at http://agilewebdevelopment.com/plugins shows several potential solutions. Don’t hesitate to share your experience if you ’ve used one of them !

blog comments powered by Disqus

Entries per category

  1. 9 pages are tagged with documentum
  2. 12 pages are tagged with events
  3. 14 pages are tagged with rails
  4. 32 pages are tagged with ruby
  5. 13 pages are tagged with sharepoint

Recent Comments

Popular Threads