-
Notifications
You must be signed in to change notification settings - Fork 111
Tutorial: securing a controller
Agenda: we’ve got a standard REST controller (ProductsController) and want to discover how access restrictions can be applied to it (using acl9 of course!)
Here’s the controller (with omitted action bodies for brevity):
class ProductsController < ApplicationController
def index
# ...
end
def show
# ...
end
def new
# ...
end
def edit
# ...
end
def create
# ...
end
def update
# ...
end
def destroy
# ...
end
endAdd a line to the config/environment.rb file and install the gem.
config.gem "acl9", :source => "http://gemcutter.org", :lib => "acl9"When everyone is allowed to do everything, it’s also access control. So we’ll start from this.
All access rules in controllers are put into access_control block. The all-permissive variant will be:
class ProductsController < ApplicationController
access_control do
allow all
end
def index
# ...
end
# ...other actions...
endallow all makes sense here, doesn’t it?
This controller should work, if it doesn’t, you might have some problems with acl9 installation.
So, you actually don’t want your competitor to come and delete all your products, replacing them with theirs! A simple scheme could work: you’re the only registered user, you do the stuff, and others are just anonymous users with no rights.
How about this?
access_control do
allow logged_in
endSeems cool. Only logged in users can… but wait! Now nobody can even see your products (except you)! Something must be done:
access_control do
allow logged_in
allow anonymous, :to => [:index, :show]
endThe second rule to the rescue. In this case anonymous users can see, but not create, edit, or destroy products.
Short answer: not controller.send(:current_user).nil?.
This means you should use some authentication solution (authlogic, restful_authentication, clearance, or roll out your own current_user implementation).
Note_: if the method is named differently (current_account, or even @current_jedi_that_shouldbe@), acl9 can be configured to handle that (see the docs).
Answer: Acl9::AccessDenied exception is raised.
You’ll probably handle this in the ApplicationController, in the following manner:
class ApplicationController < ActionController::Base
rescue_from 'Acl9::AccessDenied', :with => :access_denied
# ...other stuff...
private
def access_denied
if current_user
render :template => 'home/access_denied'
else
flash[:notice] = 'Access denied. Try to log in first.'
redirect_to login_path
end
end
endLet’s imagine your company has had a very successful period, the product count grows every day, but you’re still the only user who can add, or edit products in any way. This task should now be delegated, right? But you’re still afraid that some registered user will come and destroy (intentionally or occasionally) all products, ruining your work.
That is, you want to let other registered users create and edit products, but reserve the destroy for yourself, the admin.
Here you are:
access_control do
allow :admin
allow logged_in, :except => :destroy
allow anonymous, :to => [:index, :show]
endNow only the admin can do everything, other registered users are not allowed to destroy.
Short answer: current_user && current_user.has_role?(:admin, nil).
You’ll get the has_role? method in your User class if you put acts_as_authorization_subject inside it and create appropriate tables (see the docs).
So far so good. Still, any logged in user can edit products now, even if another user has created them.
And your logged in customers… right! They can edit the desired product, lowering the price to some affordable value, say $0.01, and then buy it. Oh my!
We’ll fix this situation by using object roles.
access_control do
allow all, :to => [:index, :show]
allow :admin
allow logged_in, :to => [:new, :create]
allow :owner, :manager, :of => :product, :to => [:edit, :update]
endWhat have we got here? Everyone can go for index and show actions (allow all means just that). Admin can do anything he wants to.
Other logged in users can create new products. But only product owner and product manager are allowed to edit/update.
What does it mean, :product? Is it a role? Nope. Basically it’s a reference to @product, the instance variable in the controller.
Does acl9 set @product variable automagically? Nope. You’ll need a before_filter for that.
class ProductsController < ApplicationController
before_filter :load_product, :only => [:edit, :update, :destroy, :show]
access_control do
allow all, :to => [:index, :show]
allow :admin
allow logged_in, :to => [:new, :create]
allow :owner, :manager, :of => :product, :to => [:edit, :update]
end
# ...
private
def load_product
@product = Product.find(params[:id])
end
endIn this case @product variable will be initialized before the access control checks are executed, and that’s exactly what we need!
So, what’s an object role? It’s a role (owner & manager here), tied to a specific object, with specific class and id.
If you use acl9 builtin role subsystem, has_role! method is the thing. Let’s assign owner role in create:
class ProductsController < ApplicationController
# ....
def create
@product = Product.new(params[:product])
if @product.save
flash[:notice] = 'Product created.'
current_user.has_role!(:owner, @product) # <--------- assign the role
redirect_to(@product)
else
render :new
end
end
# ....
endYou may unassign a role as well:
current_user.has_no_role! :owner, @productA @product object might even have several owners this way!
When assigning a role on an object, acl9 will create an entry in the table roles for the given role name (owner), for the authorization_type (Product) and the authorization_id (@product.id). It will also insert a record in roles_users to link the id of @current_user to the role.
So, any registered and logged in user can create his own products now. It’s sorta Web 2.0-ish when your customer creates a product that he wants to buy from you, but let’s explore how to implement a more traditional way to do things.
We want to allow product creation only to selected users, how do we do that? One solution is to introduce product_manager role:
before_filter :load_product, :only => [:edit, :update, :destroy, :show]
access_control do
allow all, :to => [:index, :show]
allow :admin
allow :product_manager, :to => [:new, :create, :destroy]
allow :owner, :manager, :of => :product, :to => [:edit, :update]
endDestroy right also goes to product_manager. That’ll be enough, but acl9 provides us with a nicer variation:
before_filter :load_product, :only => [:edit, :update, :destroy, :show]
access_control do
allow all, :to => [:index, :show]
allow :admin
allow :manager, :of => Product, :to => [:new, :create, :destroy]
allow :owner, :manager, :of => :product, :to => [:edit, :update]
endThe manager of the Product class itself! Makes sense, because new and create actions operate on Product, not on its instances. Note that Product manager isn’t automatically a @product manager (a class role doesn’t always imply an object role).
So, you can assign roles not only for objects, but also for classes:
@good_user.has_role!(:manager, Product)
@bad_user.has_no_role!(:manager, Product)We’ve successfully enforced access rules on our ProductsController and finally it qualifies to Mostly High Standards of Security (that’s a joke, you get it).
To top it off, I’ll show you another way to express the same set of rules in acl9 DSL:
before_filter :load_product, :only => [:edit, :update, :destroy, :show]
access_control do
actions :index, :show do
allow all
end
actions :new, :create, :destroy do
allow :manager, :of => Product
allow :admin
end
actions :edit, :update do
allow :owner, :manager, :of => :product
allow :admin
end
endThis may be called action-oriented approach (as compared with the role-oriented approach used before). These two can be mixed together though.
You’re welcome to acl9-discuss Google Group.
- Home
- Role Subsystem
- Access Control Subsystem
- Legacy Docs (some faults/errors may exist)