Complex conditions in CanCanCan can method - ruby-on-rails

If I have user,client and request models as follows:
#user.rb
#client.rb
has_one :user
has_many :requests
#request.rb
belongs_to :client
I use user model for CanCanCan authentication.
Inside ability class i want to specify ability for client. I want to user to allow read,update only for requests that belong to him.
Her is what i try:
def client
can [:read,:update], [Request], ['client_id = ?', user.client_id] do |client|
......something here
end
end

can [:read, :update], Request, :client_id => user.id

here is the simplest option:
can [:read, :update], Request, :client_id => user.id
if you have more complex abilities than this then you can do:
can [:read, :update], Request do |request|
request.client_id == user.id
end

Related

Rails 4: CanCanCan abilities with has_many :through association

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'
...

Cancan :create ability

I have the following code:
#/app/models/users/user.rb
class Users::User < ActiveRecord::Base
has_many :phones, class_name: "Users::Phone"
end
#/app/models/users/phone.rb
class Users::Phone < ActiveRecord::Base
belongs_to :user, class_name: "Users::User"
attr_accessible :phone
end
#/app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :read, :all
unless user.nil? #logged_in
if user.is? :admin
can :manage, :all
else
can :create, Users::Phone, user_id: user.id
end
end
end
end
I wanna check ability for create only their own phones for users
#/app/views/users/users/show.html.slim
- if can? :create, Users::Phone.new
a[href="#{new_user_phone_path(#user)}"] Add phone
Thats does not work, because I should pass user_id to phone model (like Users::Phone.new user_id: user.id), but I can't do that since Phone's mass assignment.
So how I can check :create phones ability for users?
I do something similar to this in my app by making Ability aware of the underlying parameter structure. You have a few options depending on your requirements. So in your controller you'd have approximately:
def create
#phone = Users::Phone.new(params[:users_phone])
# Optional - this just forces the current user to only make phones
# for themselves. If you want to let users make phones for
# *certain* others, omit this.
#phone.user = current_user
authorize! :create, #phone
...
end
then in your ability.rb:
unless user.nil? #logged_in
if user.is? :admin
can :manage, :all
else
can :create, Users::Phone do |phone|
# This again forces the user to only make phones for themselves.
# If you had group-membership logic, it would go here.
if phone.user == user
true
else
false
end
end
end
end

rails and cancan - how to restrict the company show action to users owner in has_many through association

i'm trying to solve an user's ability problem with cancan gem.
company and users are associated through user_company_assignment in such a way that a company has many user and the users has and belongs to many companies
I would like to restrict the show action of a company only to those users associated with the company. below there is the code of the two models and a snip of ability.rb with the initialize role inheritance and the method for the seller user, but this is not working, it show me always the company detail.
Company.rb
has_many :user_company_assignments
has_many :user, :through => :user_company_assignments
User.rb
has_many :user_company_assignments
has_many :companies, :through => :user_company_assignments
Ability.rb
def initialize(user)
#user = user || User.new # for guest
#user.roles.each { |role| send(role.name.downcase) }
end
def seller
can :manage, :all
cannot :destroy, :all
can :show, Company do |company|
company.user_ids.include? #user.id
end
end
Your error is due to ability precedence: https://github.com/ryanb/cancan/wiki/Ability-Precedence
This line overrides all following abilities: can :manage, :all
Since you've already stated that a seller can manage all, the seller can perform any kind of action on a Company, regardless of the other can statement.
One solution would be to use cannot, as you did with :destroy. It will override the :manage, :all clause.
def seller
can :manage, :all
cannot :destroy, :all
cannot :show, Company do |company|
!company.user_ids.include? #user.id
end
end
You need to call the load_and_authorize_resource method in your controller.
Ok jesper, i have changed my ability.rb and it works but i'm not sure that this is the best method to set the ability, it is strange the i need to specify each Models that a seller has the permission to the show action. tell me if is it the best way to do that:
Ability.rb
def seller
can [:index, :create], :all
cannot :destroy, :all
can :show, Company do |company|
company.user_ids.include? #user.id
end
can :show, [Report, Client]
end

Nested polymorphic relationships CanCan

I have a polymorphic relationship between Responses and Posts, Calls & Meetings:
class Response < ActiveRecord::Base
belongs_to :responseable, polymorphic: true
...
end
class Post < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
class Call < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
class Meeting < ActiveRecord::Base
has_many :responses, as: :responseable, dependent: :destroy
...
end
I am using CanCan to define my abilities use the Nested Resource feature to authourize the viewing of responses dependent on their parents ability:
class ResponsesController < ApplicationController
before_filter :authorize_parent
load_resource :post
load_resource :call
load_resource :meeting
load_and_authorize_resource :response, :through => [:post, :call, :meeting]
...
private
def authorize_parent
authorize! :read, (#post || #call || #meeting)
end
end
All the abilities are working as they should using the standard actions in the controller.
However, I have an action in my Responses controller that is used to poll for new responses by a JS script every 15 seconds:
def polling
current_user_id = params[:current_user_id]
responseable_type = params[:responseable_type]
klass = [Post, Call, Meeting].detect { |c| responseable_type == c.name }
#responseable = klass.find(params[:responseable_id])
undivided_millisecond_epoch_time_in_integer = params[:after]
undivided_millisecond_epoch_time_in_decimal = (undivided_millisecond_epoch_time_in_integer).to_d
divided_millisecond_epoch_time_in_decimal = (undivided_millisecond_epoch_time_in_decimal / 1000000).to_d
#responses = #responseable.responses.where("created_at > ? AND user_id <> ?", Time.at(divided_millisecond_epoch_time_in_decimal), current_user_id)
end
No matter what I try I can not get this to work. I.e. I just get the response "You are not authorized to access this page". I presume that I need to add something to get this custom action working, but I'm a bit lost as to where and what.
Any help greatly appreciated.
EDIT: Added the details in my abilities file:
if user.role == "client"
can :index, Post, :user_expert_private => false
can :index, Post, :user_expert_private => true, :user_id => user.id
can :show, Post, :user_expert_private => false, :countries => { :id => user.country_ids}
can :show, Post, :user_expert_private => true, :user_id => user.id
can :create, Post
can :edit, Post, :user_id => user.id
can :update, Post, :user_id => user.id
can :read, Response
can :create, Response
can :polling, Response
can :read, Call, :user_id => user.id
can :create, Call, :user_id => user.id
can :edit, Call, :user_id => user.id
can :update, Call, :user_id => user.id
can :read, Meeting, :user_id => user.id
can :create, Meeting, :user_id => user.id
can :edit, Meeting, :user_id => user.id
can :update, Meeting, :user_id => user.id
can :responses, :polling
can :posts, :autocomplete
end
According to this, at the end of your polling method, you should be able to place this at the end of your polling method:
authorize! :read, #responses
I believe the reason is that you have authorized use of the Response object only under the auspices of the parent objects.
edit based on abilities:
perhaps something like
can :polling, Response, :user_id => user.id
I am doing some work in another project, and this page is probably going to be of use. I will update this answer if/when it proves to be correct.

Rails cancan authorizing nested resources

I have Projects resource which is nested in Users resource.
My Cancan Ability class is:
class Ability
include CanCan::Ability
def initialize(user)
#everyone
can :read, Project
if user.blank?
# guest user
...
else
#every signed in user
case user.role
when User::ROLES[:admin]
#only admin role user
can :manage, :all
when User::ROLES[:member]
#only member role user
can :update, User, :id => user.id
can [:create, :update, :destroy], Project, :user_id => user.id
else
end
end
end
end
And Projects controller:
class ProjectsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :projects, :through => :user, :shallow => true
...
end
I have few questions:
Is it possible to deny :read User and allow to :read Project, so that everyone could access /users/10/projects, but not /users/10 or /users?
How can I deny user accessing :new action with other user_id? For example, if I add
#everyone
can :read, User
can :read, Project
this code allows user with id 42 to access /user/41/projects/new.
Solved it by doing:
class Ability
include CanCan::Ability
def initialize(user)
#everyone
can :read, Project
can :read, User # required to access nested resources
cannot :index, User
cannot :show, User
if user.blank?
# guest user
...
else
#every signed in user
case user.role
when User::ROLES[:admin]
#only admin role user
can :manage, :all
when User::ROLES[:member]
#only member role user
can :update, User, :id => user.id
can :manage, Project, :user => { :id => user.id }
else
end
end
end
end

Resources