In my project, I have a user class working with Devise authentication. Each user have one role associated with. For exemple, a manager is the role #1.
Role is a class and there is an association beetween role and user based on role_id. So a manager will have a role_id of 1.
I installed cancancan gem to manage permission for each user. Only a manager can create user account. So, I wrote this in the ability class:
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role_id == 1
can :manage, :user
else
cannot :manage, :user
end
end
In my user controller, I have this:
def new
#user = User.new
authorize! :manage, #user
end
The problem is when I login with my manager and try to access to new user page, I receive the message from cancancan saying that I don't have the permission to access the page.
I don't know if it's my condition or when I'm trying to access to user.role_id or...
Thanks for your help.
See cancancan gem : https://github.com/CanCanCommunity/cancancan
Edit:
It's normal if my IDE says that it cannot find current_user?
Also, if you check on github, there is an example of how to use cancancan:
As you see, there is an #article after the read permission. So I don't think I need to put current_user instead of #user
Finally:
Problem was the :user that was not correct. When it's a model, you need to write a capital letter first for the name of the model without ":". So instead of :user, it should be User
current_user relies on a Rails session. Since this is your #new action on your Users Controller, you don't have an active session, thus no current_user.
Related
I want to give rights to users in my rails app. I have 'admin' who can create, update and delete all posts and comments, 'user' who can create and update only his own comments, and 'guest' who can do none of these. For this I use the gems 'devise' and 'cancancan'. I understand the 'devise' gem, but I don't understand 'cancancan'.
In the class ability.rb, how can I write rights for these users (admin, user, guest)?
Cancancan lets you only define permissions for given context. This context might be a user role which is not a part of cancancan and hence roles have to be defined by yourself.
There are various ways to define user role, e.g.
as a Role model,
Rails enum,
as suggested here,
as a string attribute of User model.
It all depends of the use case. An example of how to define abilities can be found here. In your case, it would look like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.reviewer? #Just a logged user
can :manage, Comment, { owner_id: user.id }
elsif user.admin?
can :manage, :all
end
end
end
class User < ActiveRecord::Base
enum role: [ :reviewer, :admin ]
end
You can refer following rails cast http://railscasts.com/episodes/192-authorization-with-cancan
If you are going to use Roles, consider the Canard gem (https://github.com/james2m/canard) which combines CanCanCan with RoleModel (https://github.com/martinrehfeld/role_model). This gives you a well organized way of stipulating roles and their abilities
For example, if you have a user model, you would set the roles as follows:
class User < ActiveRecord::Base
acts_as_user roles: [:supervisor, :manager, :writer]
end
Then you can create Ability files for each role, including a base User (not assigned any roles) and Guest (not logged in)
Canard::Abilities.for(:user) do
can :manage, User, id: user.id
cannot [:destroy], User
end
The above will allow a user to update their own information but will not allow them to delete themselves. They also will not have access to any other user record.
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 Rails Admin with CanCan support in my rails app. I'm confused on one issue though. How does CanCan know what user is signed in? For example, my users can have different roles and through CanCan I assign roles for certain access into each table. When I go to localhost:3000/admin, I receive the error
CanCan::AccessDenied in RailsAdmin::MainController#dashboard
My Ability.rb file
def initialize(user)
if user and user.role.eql? :super_admin
can :manage, :all # allow superadmins to do anything
elsif user and user.role.eql? :admin
can :manage, [Location, School] # allow admins to manage only Locations and Schools
end
end
So what do I do so that user's have the ability to sign in into Rails Admin? Do I have to manually create it?
By default, CanCan will use whatever is returned by current_user. If you are using Devise within a namespace though (admin for example) then Devise actually will use current_admin_user instead. You can either create a current_user method in your ApplicationController (or some other base controller) that returns current_admin_user or overwrite the current_ability method to instantiate the Ability with current_admin_user instead.
(this is all assuming your Devise is using a namespace. By default Devise will use current_user)
You need to have a current_user method available in your controller. Assuming you have that, if you aren't signed you won't have access to a current user, so you'll need to assign a user in your ability file if it doesn't exist. In your initialize method, add user ||= User.new to make the assignment if a user doesn't already exist.
def initialize(user)
user ||= User.new
if user and user.role.eql? :super_admin
can :manage, :all # allow superadmins to do anything
elsif user and user.role.eql? :admin
can :manage, [Location, School] # allow admins to manage only Locations and Schools
end
end
Post: hidden: boolean
I want the logged in user could see all the posts, and the non-logged-in user only have access to posts whose hidden fields are false.
So I write like this in cancan's Ability Model:
if user_signed_in?
can :read, Post
else
can :read, Post, :hidden => false
end
but accessing the helper user_signed_in is not allowed in Model.
As stated in this question: Rails 3 devise, current_user is not accessible in a Model ?. While we could using some tricks to access the helper, its not proper to do that
So, how could I authorize the not-logged-in user properly? Or just use "Include" to use this helper ??
Or should I put this in authentication part? but how?
All you need to do is this:
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Post, :hidden => false
if User.exists?(user)
can :read, Post
end
end
With devise, the current_user helper method returns the current user when logged in but returns nil when not logged in. It is available in the controllers and views. By default, CanCan does all authorization checks against the return of the current_user method.
Now whenever the can? method is called from a view or a controller, the return of current_user will be passed to a new instance of Ability as the local variable user.
To check if the user was logged in, I choose to useUser.exists?(). It's a class method for ActiveRecord::Base that will check if that user object is persisted in the database. Any other way will work just as well though. For instance, this would work just as well or better:
if user.encrypted_password
can :read, Post
end
This will check if the default devise password field exists for the user instance. If you haven't done anything too crazy, this will only return a value if the user is logged in. If the second option works for your situation, it may be superior because you won't even have to query the database.
Semi-relevant tip, check out a role handling gem like Rolify. It is great when used in conjunction with CanCan.
I use devise for authentication and only an admin can create a user.
I use cancan to assign roles to the user during user creation.
I want the admin to view all the users roles and the admin should be able to edit the roles of the users.
How can I do this?
Check out https://github.com/marklocklear/devise_multi_tentant. In ability.rb I have...
class Ability
include CanCan::Ability
def initialize(user)
can :manage, :all if user.role == "admin"
end
end
You can add a role attributes for User model.
Then, when user go to admin dashboard, you can check user's role. If user isn't admin, redirect to another page (using before_filter :function)
But, instead doing everything manually, you can use gem cancan instead, it's so popular and easy to use!
You can find the document here: GitHub
Or in rails cast episodes 192
Basically, it'll create a ability model, and you will declare privilege for user, a sample ability class
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :update, User
end
end
end
After that, you can authorize for user in action, controller or views, like the example see this link
PS: sorry if my English was too bad, thanks:)