I'm using Rails 3 with Devise for user auth. Let's say I have a User model, with Devise enabled, and a Product model, and that a User has_many Products.
In my Products controller I'd like my find method to be scoped by current_user, ie.
#product = current_user.products.find(params[:id])
unless the user is an admin user, i.e. current_user.admin?
Right now, I'm running that code in almost every method, which seems messy:
if current_user.admin?
#product = Product.find(params[:id])
else
#product = current_user.products.find(params[:id])
end
Is there a more elegant/standard way of doing this?
I like to do this as follows:
class Product
scope :by_user, lambda { |user|
where(:owner_id => user.id) unless user.admin?
}
end
this allows you to write the following in your controller:
Product.by_user(current_user).find(params[:id])
If you're running this code in a lot of your controllers, you should probably make it a before filter, and define a method to do that in your ApplicationController:
before_filter :set_product, :except => [:destroy, :index]
def set_product
#product = current_user.admin? ? Product.find(params[:id]) : current_user.products.find(params[:id])
end
I don't know what you use to determine if a user is an admin or not (roles), but if you look into CanCan, it has an accessible_by scope that accepts an ability (an object that controls what users can and can't do) and returns records that user has access to based on permissions you write yourself. That is probably really what you want, but ripping out your permissions system and replacing it may or may not be feasible for you.
You could add a class method on Product with the user sent as an argument.
class Product < ActiveRecord::Base
...
def self.for_user(user)
user.admin? ? where({}) : where(:owner_id => user.id)
end
Then you can call it like this:
Product.for_user(current_user).find(params[:id])
PS: There's probably a better way to do the where({}).
Related
I'm using the pundit gem and trying to figure out how to use it to prevent access to an index page that belongs to a user other than the current_user.
The examples only talk about how to scope the results to the current_user but no how to actually prevent access to the page itself if the current_user is NOT the owner of the record.
Any help appreciated
Thanks
Maybe you want something like this? (For class ModelName)
# /policies/model_name_policy.rb
class ModelNamePolicy
attr_reader :current_user, :resource
def initialize(current_user, resource)
#current_user = current_user
#resource = resource
end
def index?
current_user.authorized_to_edit?(resource)
end
end
# /models/user.rb
class User < ActiveRecord::Base
def authorized_to_edit?(resource)
admin? | (id == resource.created_by) # Or whatever method you want to call on your model to determine ownership
end
end
EDIT: Note that you will also need to call authorize from your controller to invoke the policy.
I have a Campaign model which has many Applicants. I'm currently nesting Applicants within Campaigns. I'm trying to authorize a user to applicants#index based on if they are the owner of the campaign.
resources :campaigns do
..
resources :applicants do
..
end
end
What's the best way to secure the applicants#index action in Pundit? Ideally I would like to pass the #Campaign to authorize.
class ApplicantsController < ApplicationController
def index
#applicants = Applicant.where(campaign: #campaign)
authorize #campaign
respond_with(#applicants)
end
But this results in Pundit looking for campaign_policy.
I'd probably use the show? method on the CampaignPolicy
authorize #campaign, :show?
where show? (call it whatever you want...manage?, etc...) would be
def show?
record.user_id = user.id
end
Just because the resource being displayed is a list of applicants doesn't mean you need to authorize against them directly. If your logic requires authorizing the owner of the campaign, do that.
Finally, if this is some wide-spread, common thing in your application, you might consider creating some value object to wrap your current user and campaign in.
class CurrentState < Struct.new(:user, :campaign); end
and then override the pundit_user method.
def pundit_user
CurrentState.new(current_user, Campaign.find(params[:campaign_id])
end
See Additional Context in the Pundit docs.
I am using devise for authentication and have an association between users (has_many :products) and products model (belongs_to :user).
My routes file is
resources :users do
resources :products
end
Now what happens is, user with id 3 at /users/3/products can also see whats at /users/4/products. I want to restrict that. I dont want /users/3/products to able to see whats at /users/4/products and so on (not specific to these two users but for all). How do I do it ? Should I have a Users Controller? I dont have it right now. If i have the controller, how do I do it? I was thinking maybe redirect it?
thanks
You could add a before_filter in your products controller:
class ProductsController < ApplicationController
before_filter :user_is_current_user
...
private
def user_is_current_user
unless current_user.id == params[:user_id]
flash[:notice] = "You may only view your own products."
redirect_to root_path
end
end
end
Also, in the products controller you could retrieve only products belonging to the current_user:
def index
#products = current_user.products # will fetch all products with a user_id matching the current user's
end
If you used the above you wouldn't really need a user's ID in the URL, you could use a path like /users/products or just /products.
I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end
I need to make sure a user has the correct permissions before allowing them to edit an employee's information. Specifically the user has to be an admin and the user must belong to the same company as the employee. What's the best way to do something like this?
def EmployeesController < ApplicationController
before_filter :requires_admin_from_company(cid)
# Only allow access to this if user.admin is true and user.company_id is equal to employee.company_id
def update
# Somehow pass #employee.company_id into admin
#employee = Employee.find(params[:id])
#employee.update_attributes(params[:employee])
end
def requires_admin_from_company(cid)
if !#current_user.admin? || #current_user.company_id != cid
redirect_to login_url
end
end
end
How about
before_filter lambda{ requires_admin_from_company(params[:cid]) }, :only => :create
I've found Authorization with CanCan to be very helpful in these situations