I have a db setup where there are many users, which have roles of member or admin. Each user has many cars. Each car has many timeslips
So, how do i limit a user's ability to edit a Timeslip only if he is the owner of the parent car.
In CanCan:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :member
can :manage, Car, :user_id => user.id
can :manage, Timeslip, :car => {:user_id => user.id}
end
end
end
so the line can :manage, Timeslip, :car => {:user_id => user.id} is where i need some help.
Because Timeslip is an association/child of Car, i need to check that its parent car.user_id = the Cancan user.id
I thought how i wrote this is in line with the CanCan docs, but where have I gone wrong?
There might be a shorter way to write it, but this will work:
can :manage, Timeslip do |timeslip|
timeslip.car.user_id == user.id
end
This is what's working for me: can :manage, Timeslip, car: { roles: { id: user.role_ids } }
Related
I want the Admin user to not have the ability to create users with the role of Super Admin but still be able to create other Admins and Regular Users. How do I accomplish this? Here is my Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
if user.super_admin?
can :manage, :all
elsif user.admin?
can :manage, [Article, Comment]
can [:destroy, :update], User, :role_id => 2 # If Admin
can [:destroy, :update], User, :role_id => 3 # If User
can :read, User
can :create, User
elsif user.user_regular?
#cannot :read, ActiveAdmin::Page, :name => "Dashboard"
#can :manage, :all
end
end
end
Use cannot with in admin block like cannot :creat, User, :role_id => 1 # let 1 is super admin role id. You can get more info about combine ability at here
I have a Rails app with the following models:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
For a given calendar, a user has a role, which is define in the administration join model.
For each calendar, a user can have only one of the following three roles: Owner, Editor or Viewer.
These roles are currently not stored in dictionary or a constant, and are only assigned to an administration as strings ("Ower", "Editor", "Viewer") through different methods.
Authentication on the User model is handled through Devise, and the current_user method is working.
In order to only allow logged-in users to access in-app resources, I have already add the before_action :authenticate_user! method in the calendars and administrations controllers.
Now, I need to implement a role-based authorization system, so I just installed the CanCanCan gem.
Here is what I want to achieve:
All (logged-in) users can create new calendars.
If a user is the owner of a calendar, then he can manage the calendar and all the administrations that belong to this calendar, including his own administration.
If a user is editor of a calendar, then he can read and update this calendar, and destroy his administration.
If a user is viewer of a calendar, then he can read this calendar, and destroy his administration.
To implement the above, I have come up with the following ability.rb file:
class Ability
include CanCan::Ability
def initialize(user, calendar)
user ||= User.new
calendar = Calendar.find(params[:id])
user can :create, :calendar
if user.role?(:owner)
can :manage, :calendar, :user_id => user.id
can :manage, :administration, :user_id => user.id
can :manage, :administration, :calendar_id => calendar.id
elsif user.role?(:editor)
can [:read, :update], :calendar, :user_id => user.id
can :destroy, :administration, :user_id => user.id
elsif user.role?(:viewer)
can [:read], :calendar, :user_id => user.id
can :destroy, :administration, :user_id => user.id
end
end
end
Since I am not very experimented with Rails and it is the first time I am working with CanCanCan, I am not very confident with my code and would like some validation or advice for improvement.
So, would this code work, and would it allow me to achieve what I need?
UPDATE: with the current code, when I log in as a user, and visit the calendars#show page of another user's calendar, I can actually access the calendar, which I should not.
So, obviously, my code is not working.
Any idea of what I am doing wrong?
UPDATE 2: I figured there were errors in my code, since I was using :model instead of Model to allow users to perform actions on a given model.
However, the code is still not working.
Any idea of what could be wrong here?
UPDATE 3: could the issue be caused by the fact that I use if user.role?(:owner) to check if a user's role is set to owner, while in the database the role is actually defined as "Owner" (as a string)?
UPDATE 4: I kept on doing some research and I realized I had done two mistakes.
I had not added load_and_authorize_resource to the calendars and administrations controllers.
I had defined two attributes two parameters — initialize(user, calendar) — instead of one in my initialize method.
So, updated both controllers, as well as the ability.rb file as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role?(:owner)
can :manage, Calendar, :user_id => user.id
can :manage, Administration, :user_id => user.id
can :manage, Administration, :calendar_id => calendar.id
elsif user.role?(:editor)
can [:read, :update], Calendar, :user_id => user.id
can :destroy, Administration, :user_id => user.id
elsif user.role?(:viewer)
can [:read], Calendar, :user_id => user.id
can :destroy, Administration, :user_id => user.id
end
end
end
Now, when I try to visit a calendar that does not belong to the current_user, I get the following error:
NoMethodError in CalendarsController#show
undefined method `role?' for #<User:0x007fd003dff860>
def initialize(user)
user ||= User.new
if user.role?(:owner)
can :manage, Calendar, :user_id => user.id
can :manage, Administration, :user_id => user.id
can :manage, Administration, :calendar_id => calendar.id
How I can fix this?
There is no such method role? the User model. The Cancancan documentation is at fault for assuming such a method exists in the examples.
To fix this, you should instead do:
if user.role == 'Owner'
...
elsif user.role == 'Editor'
...
elsif user.role == 'Viewer'
...
I have three roles of AdminUsers:
admin
editor
student
I need the editor to have the ability to manage(add/view/delete) AdminUser accounts that its role is student.Yet, he can't see whose role is admin or editor.
With the following code, he could view only without creation/deletion/edit
if user.role?(:admin)
can :manage, :all
elsif user.role?(:editor)
can :read, ActiveAdmin::Page, :name => "Dashboard"
can :manage, :all
cannot :manage, AdminUser
can :manage, AdminUser, :role => :student
else
can :read, :all
end
If I understand you correctly, you're saying that a user who is an editor should be able to manage the account of another user who is a student. If that's the case, this should work:
if user.role?(:admin)
can :manage, :all
elsif user.role?(:editor)
can :read, ActiveAdmin::Page, :name => "Dashboard"
can :manage, AdminUser, :role => :student
else
can :read, :all
end
I am using cancan for permissions and I would like it so users cannot see other users when visiting their profile/users page. User should only be able to see themselves.
In my ability.rb file I have
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? :rookie
can [:update, :destroy], [Album, Photo, User]
can :read, :all
end
can :manage, Album, :profile => { :user_id => user.id }
can :manage, Photo, :profile => { :user_id => user.id }
can :manage, Video, :profile => { :user_id => user.id }
can :manage, Comment, :blog => { :profile => { :user_id => user.id } }
can :manage, User, :id => user.id
end
end
In my users_controller I have
class UsersController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
def index
#user = current_user
end
def show
#user = User.find(params[:id])
end
end
The above usually works but since user is the primary model I am not sure how I can resolve this. Rails gives me the error
undefined method `user_id'
It should be:
#ability.rb:
can :manage, User, :id => user.id
I am assuming that most users start with role :rookie and you have ability
can :read, :all
for users with role :rookie. Then this means that all :rookie users will be able to read
all resources.
I'm having some trouble defining permissions for my albums#index action. The path to it is /user/:user_id/albums - this is the ability for my :show action (:read => [:index, :show]) which is working well. The path to it is /user/:user_id/albums/:id/.
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
I'm not sure how to write a similar rule for the index action, or if I even want to use CanCan here. The rule is:
current_user MUST be .friends_with?(user_id) to view any albums belonging to user_id.
user_id is of course taken from params[:user_id]. Note: /user/eml/albums/ would be the path, I'm not fetching users by their .id but by their .username!
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new # for guest, probably not needed
#user.roles.each { |role| send(role) }
end
def user
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
can :manage, Album do |album|
album.user_id == #user.id
end
end
end
UPDATE:
Turns out the solution is really simple, I was just not paying attention to my routes:
resources users do
resources albums
end
In the controller that becomes pretty easy then:
load_and_authorize_resource :user, :find_by => :username
load_and_authorize_resource :album, :through => :user
And the rule:
can :read, Album, :user_id => #user.friend_ids # I don't need #user.id
I'm not perfectly happy with it though, as using the user.friends_with?(other_user) method would be much quicker and doesn't have to fetch (potentially) a thousand ids from the database. Any other solution is most welcome.
On IRC you told me that the .roles_mask isn't important... then shouldn't this be something like:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :read, Album, :user_id => [user.id] + user.friend_ids
can :manage, Album, :user_id => user.id
end
end
end