I wish to allow admins to create posts with custom fields, but still wish for them to use the same create action as normal users use.
I thought to do this:
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :specialize, Post
end
end
end
Then in my controller:
def create
#post = Post.new
if can? :specialize, #post
do_fancy_things_here
end
end
The weird thing is, do_fancy_things_here is ALWAYS running regardless of if the user is an admin or not.
This is strange. The only way I deviated from the cancan manuals is that :specialize does not actually map to a controller action. Does that matter?
Your have to use authorize! :specialize, #post in your controller.
https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions
Related
I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.
I have a controller with a method like;
def show
if params[:format].eql?("pdf")
// do something
elsif params[:format].eql?("csv")
// do something
end
end
But i have users with different roles. So i use CanCan to manage access control.
Now i want X role can do the action show in controller iff params[:format].eql?("csv")
I think it can be like ;can :show, resource if params[:format].eql?("csv"). So how can i send parameters to ability.rb?
Any idea?
Thanks.
In ApplicationController add the following:
# CanCan - pass params in to Ability
# https://github.com/ryanb/cancan/issues/133
def current_ability
#current_ability ||= Ability.new(current_user, params)
end
The most current answer is in the CanCan wiki: https://github.com/ryanb/cancan/wiki/Accessing-Request-Data
can takes two arguments: first is type of action that user is trying to perform on a resource, second is resource (can be class name or instance variable) itself. If you have your Ability set correctly, you should be able to do something like this:
def show
if params[:format].eql?("pdf")
// do something
elsif params[:format].eql?("csv")
if can? :read, resource
#do stuff
end
end
end
Don't forget that you have to have your user authenticated before running any CanCan checks.
can? method only returns true or false. I normally like to use authorize! method to check abilities. Unlike can, it would rise CanCan::AccessDenied error that you can rescue and process gracefully. Something in the lines of:
#models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :admin
can :manage, :all
elsif user.role? :hiring_manager
can [:read, :update], Post, user_id: user.id
end
end
end
#controllers/posts_controller.rb
class PostsController < ApplicationController::Base
before_filter :authenticate_user
def show
#post = Post.find(params[:id])
authorize! :read, #post # will thorow an exception if not allowed
end
end
Then, I just catch the exception on ApplicationController level.
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'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({}).
I added new action to my restful resources how can I authorize it with cancan.
Pages controller:
load_and_authorize_resource
def index
end
def show
end
def new
end
def create
end
def edit
end
def update
end
def destroy
end
def mynewaction
end
Ability model:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :create, Page
can :mynewmethod, Page #does it work?
end
Yes I believe it works for things other than the CRUD methods, although this is just from looking at some of the documentation, check out here and the actual documentation by ryanb here.
You should especially look at that first link that says that the load_and_authorize_resource will apply to all methods in the controller, even ones other than the usual CRUD ones.
I think the easiest way is just to test it out, does it authorize correctly when you fire it up? Nothing better than to try.