views:

535

answers:

3

I have a number of resources (Trips, Schedules, etc) with actions that should be limited to just the resource's owner.

How do you implement code with a #require_owner method defined in ApplicationController to achieve this? Ideally, the code will look up the inheritance chain for the owner so the before_filter will work on a :comment that belongs_to :trip that belongs_to :user.

class TripsController < ApplicationController
  belongs_to :member
  before_filter :require_owner

  ...

end
+2  A: 

There's a few different ways to do this. You should definitely check out the acl9 plugin (http://wiki.github.com/be9/acl9/tutorial-securing-a-controller).

If you decide you want to do this yourself, I'd suggest doing something like:

class Trip < ...
    def owned_by?(user)
        self.user == user
    end
end 

class Comment < ...
    delegate :owned_by?, :to => :trip
end

# in your comment controller, for example
before_filter :find_comment
before_filter :require_owner
def require_owner
    redirect_unless_owner_of(@commemt)
end

# in your application controller
def redirect_unless_owner_of(model)
    redirect_to root_url unless model.owned_by?(current_user)
end

Forgive me if there are any syntax errors =) I hope this helps!

jonnii
Wouldn't it have to be an after_filter as @comment will not be set yet?
Jon
Negative, filters are run in thee order that they are defined, which in this case means the comment will be set and the permissions will be checked. This kind of stuff is better done with something like cancan or acl9, tbh.
jonnii
Also, I don't think I've ever used an after_filter ever.
jonnii
A: 

Acl9 is a authorization plugin. I'd give you the link, but I don't have cut and paste on my iPhone. If no one else provides the link by the time I get to a computer, I'll get it for you. Or you can google. Whichever. :)

I have only just started using it, but it has an extremely simple interface. You just have to create a roles table and a roles_user. Let me know how it goes if you decide to use it.

Elizabeth Buckwalter
+3  A: 

I don't fully follow the description (would a comment really be owned by the trip owner?), but expanding slightly on jonnii's answer, here is an example that restricts the trip controller:

class ApplicationController < ActionController::Base
  ...
protected
  # relies on the presence of an instance variable named after the controller
  def require_owner
    object = instance_variable_get("@#{self.controller_name.singularize}")
    unless current_user && object.is_owned_by?(current_user)
      resond_to do |format|
        format.html { render :text => "Not Allowed", :status => :forbidden }
      end
    end
  end
end

class TripsController < ApplicationController
  before_filter :login_required # using restful_authentication, for example
  # only require these filters for actions that act on single resources
  before_filter :get_trip, :only => [:show, :edit, :update, :destroy]
  before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
  ...
protected
  def get_trip
    @trip = Trip.find(params[:id])
  end
end

Assuming the model looks like this:

class Trip < ActiveRecord::Base
    belongs_to :owner, :class_name => 'User'
    ...
    def is_owned_by?(agent)
      self.owner == agent
      # or, if you can safely assume the agent is always a User, you can 
      # avoid the additional user query:
      # self.owner_id == agent.id
    end
end

The login_required method (provided by or relying on an auth plugin like restful_authentication or authlogic) makes sure that the user is logged in and provides the user with a current_user method, get_trip sets the trip instance variable which is then checked in require_owner.

This same pattern can be adapted to just about any other resource, provided the model has implemented the is_owned_by? method. If you are trying to check it when the resource is a comment, then you'd be in the CommentsController:

class CommentsController < ApplicationController
  before_filter :login_required # using restful_authentication, for example
  before_filter :get_comment, :only => [:show, :edit, :update, :destroy]
  before_filter :require_owner, :only => [:show, :edit, :update, :destroy]

  ...
protected
  def get_comment
    @comment = Comment.find(params[:id])
  end
end

with a Comment model that looks like:

class Comment < ActiveRecord::Base
  belongs_to :trip

  # either 
  #  delegate :is_owned_by?, :to => :trip
  # or the long way:
  def is_owned_by?(agent)
    self.trip.is_owned_by?(agent)
  end
end

Make sure to check the logs as you are doing this since association-dependent checks can balloon into a lot of queries if you aren't careful.

autodata
This is a great addition, definitely a lot think about between this and acl9.
jonnii
Great answer. I tried this solution and acl9 from the recommendations below. Ended up using acl9. Wish I could select two correct answers for this question...
Gavin