I've noticed in my site logs people are searching the Interweb Tubes for "GateKeeper" combined with other keywords that suggest they're looking for some better examples on how to use it, so I figured maybe I'd write something up to try to help.
GateKeeper is a Ruby on Rails plugin providing a natural language DSL to manage security permissions on instances of ActiveRecord classes at the Model level. Permissions may be assigned based on the current users roles, and/or by their relationships to the object in question. GateKeeper automatically intercepts all attempts to CRUD (Create, Read, Update & Destroy) instances of ActiveRecord classes and verifies the current user has permission before allowing the operation to proceed.
The main requirement of GateKeeper is that it expects that you have a 'User' class, and that it responds to the class method 'current', as in 'User.current'. If you need to use a different class (such as 'Person' or something), that can be defined in your environment.rb, but whatever it's called, it must respond to 'current' and return an object that represents the currently logged in user. So, here's a simple example of how to set that up.
Exactly how you get your users logged in is up to you and still beyond the scope of this post, but I'd recommend a SessionsController and a User.login(username, password) method. So, we'll assume that you have that setup already and that you store the 'id' of the currently logged in user in session, such as 'session[:user_id]'.
The first thing you'll want to do is have a method in your application controller that sets up the current user for the rest of the application...
class ApplicationController < ActionController::Base
before_filter :setup_user
def setup_user
User.current = User.find(session[:user_id])
end
end
Now, we need to add 'current' and 'current=' methods to User.
class User < ActiveRecord::Base
## Class Methods ##
class << self
def current
@CURRENT_USER || User.new(:username => 'guest')
end
def current=(u)
@CURRENT_USER = u
end
end
end
And that's it for User.current. Now, anywhere in your application (Models, Controllers, Helpers, and Views), you can just call User.current, and get an object that represents the currently logged in user. Now we'll go into some basic uses of GateKeeper.
If users can register new accounts on your site, then you'll need to allow 'guest' users permission to create new users. Update your User class with the following (previously shown class methods left out for brevity).
class User < ActiveRecord::Base
## Permissions ##
creatable_by_guest
#################
def is_guest?
username == 'guest'
end
end
However, if at any point your application tries to save the current user, it could potentially end up creating a user with the username 'guest', which you probably don't want. So, update the above with the following...
## Permissions ##
creatable_by_guest :unless => lambda {|new_user| new_user.is_guest? }
#################
The 'is_guest?' method above is an example of how to define dynamic 'user roles' for GateKeeper. When you say 'creatable_by_guest', GateKeeper expects to either find a 'is_guest?' instance method on User, or a Role named 'guest' belonging to User.current (see below). Here are some examples of other dynamic user roles that you might want to define for your users.
class User < ActiveRecord::Base
def is_premium_member?
premium_membership_expires_on > Time.now
end
def is_logged_in_user?
!is_guest?
end
def is_popular_user?
popularity_ratings.size > 42
end
end
class Foo < ActiveRecord::Base
## Permissions ##
creatable_by_premium_member
updatable_by_popular_user
readable_by_logged_in_user
#################
end
As I stated above, GateKeeper also looks for a Role belonging to User.current with the name of the Role used in the permission. To set this up, you'll want the following...
class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.string :name
t.timestamps
end
add_index :roles, :name
create_table :roles_users, :id => false do |t|
t.integer :user_id
t.integer :role_id
end
add_index :roles_users, :user_id
end
def self.down
drop_table :roles
drop_table :roles_users
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, if you were to create a role named 'moderator', and assign it to a few of your users, you could give those users special permissions on certain models, such as updatable_by_moderator . Take note that the role name should be all lower case and underscored so that your permissions look nice. If you have a role named 'SpecialUser', then your permissions would need look like 'readable_by_SpecialUser' which isn't beautiful code, and if the role name has spaces in it, it won't work with GateKeeper at all. 'special_user' is a much more appropriate role name for GateKeeper's purposes.
So, there you have it. I hope that helps some people get over the initial hurdle of setting up GateKeeper. I'd love to hear from anyone using GateKeeper and hear your thoughts and questions.