cancancan
Continuation of CanCan, the authorization Gem for Ruby on Rails.
This is a follow-up question on Rails 4: CanCanCan abilities with has_many :through association and I am restating the problem here since I believe context has slightly changed and after 4 updates, the code from the initial question is pretty different too.
I also checked other questions, like Undefined method 'role?' for User, but it did not solve my problem.
So, here we go: I have three 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 defined in the administration
join model (in a column named role
).
For each calendar
, a user
can have only one of the following three role
s: 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 user
s 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)
user
s can create
new calendar
s.
- If a
user
is the owner
of a calendar
, then he can manage
the calendar
and all the administration
s 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)
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 log in and try to visit any calendar page (index, show, edit), 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
I guess the problem comes from the fact that a user
does not have a role
per se, but only has a role
defined for a given calendar
.
Which explains why I get a NoMethodError
for role?
on user
.
So, the question would be: how to check a user
role
for a given calendar
?
Any idea how to make things work?
Source: (StackOverflow)
I am a little confused about how to configure CanCanCan properly.
For starters, do I have to add load_and_authorize_resource
to every controller resource I want to restrict access to?
This is what I would like to do:
- Admin can manage and access all controllers and actions
- Editor can read all, manage :newsroom, and can manage all Posts
- Member can read every Post and can create & update Posts (not edit/delete/anything else), cannot access the newsroom. The difference between an update & edit post in our business rules is that an update is creating a new post that is a child post of the current post. So it isn't an edit. Just a new record with an ancestry association.
- Guest can read every Post, but cannot create Posts nor access the Newsroom.
This is what my ability.rb
looks like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
#Admin
if user.has_role? :admin
can :manage, :all
can :manage, :newsroom
# Editor
elsif user.has_role? :editor
can :read, :all
can :manage, :newsroom
can :manage, Post
#Member
elsif user.has_role? :member
can :read, :all
can :create, Post
can :status, Post
can :update, Post do |post|
post.try(:user) == user
end
#Guest
else
can :read, :all
can :create, Post
can :status, Post
end
end
end
In my routes.rb
I have this:
authenticate :user, lambda { |u| u.has_role? :admin or :editor } do
get 'newsroom', to: 'newsroom#index', as: "newsroom"
get 'newsroom/published', to: 'newsroom#published'
get 'newsroom/unpublished', to: 'newsroom#unpublished'
end
What is happening though, is when I am logged in with a user that has not been assigned any roles (i.e. what I want to be a "Guest"), they can access the Newsroom.
When I try to edit a post with the role of :member
, it gives me a "Not authorized to edit post" error (which is correct).
I just can't quite lockdown the Newsroom
and I am not sure why.
Source: (StackOverflow)
Application I am working has different user roles client, project manager and super user and on landing page they can search for Articles and there is an advanced filter to filter out records after search. Like: Filter by Author.
I want to hide advance filter for client, for that i want to define ability using cancancan.
Currently i am doing it with model methods. These methods return true and false on the basis of user type.
client?
project_manager?
super_user?
Current Code:
<% unless current_user.client? %>
<%=link_to "Advance Search", "#" %>
<%end%>
I want to remove this and use cancancan instead of this.
<%if can? :filter, Article %>
<%=link_to "Advance Search", "#" %>
<%end%>
For this i tried
cannot :filter, Article if user.client?
But this is restricting all users to filter. Please help me out to get better and working solution. Thanks
Source: (StackOverflow)
There is the following routing:
resources :accounts, only: [:update] do
get 'search', on: :collection
resources :transactions, only: [:create]
end
Abilities:
can [:update, :search], Account
can [:create, :index], Transaction
Controller:
# Web API controller for actions on Transaction
class Api::V1::Web::TransactionsController < Api::V1::Web::ApplicationController
load_and_authorize_resource :account
load_and_authorize_resource :transaction, through: :account
def create
render json: params and return
end
end
When I try to create a new transaction I get an error:
CanCan::AccessDenied
in Api::V1::Web::TransactionsController#create
What am I doing wrong? How can I fix it? Thanks in advance.
Source: (StackOverflow)
I'm having some trouble getting ActiveAdmin to work with CanCanCan. I'm using CanCanCan version 1.9.2 and ActiveAdmin version 1.0.0.pre in a Rails 4 app. After setting up my ability class and enabling authorization checks in the rest of my app by adding load_and_authorize_resource
and check_authorization
to my ApplicationController, I get the error
protected method 'authorize!' called for #<Activeadmin::<SomeControler>> (NoMethodError)
After some searching, I ran into this this GitHub issue that looks exactly like the problem I'm having. The solution does not work at all for me, however. In config/initializers/active_admin.rb I have, among other things:
...
config.authorization_adapter = ActiveAdmin::CanCanAdapter
...
I have also ensured that I have no references to controller.authorize_resource
in any ActiveAdmin controller, yet I still get the protected method authorize! ...
error when I try to access any ActiveAdmin resources from my integration tests.
After some more trial and error and more searching, I discovered that calling load_and_authorize_resource
from ApplicationController was not recommended, and that setting ActiveAdmin's authorization_adapter to CanCanAdapter as I did above should automatically enable CanCanCan's authorization checks in ActiveAdmin, but check_authorization
fails because the resource was not authorized for every ActiveAdmin controller when load_and_authorize_resource
is removed from ApplicationController.
So what is the correct way to enable CanCanCan's authorization checks for my ActiveAdmin controllers? How should I integrate CanCanCan and ActiveAdmin so that non admin users can't access any ActiveAdmin resources?
I also posted this question to the ActiveAdmin mailing list but got no responses. Any help would be greatly appreciated.
Source: (StackOverflow)
I am beginner in Rails world, so hoping I will be able to find an answer here.
The project that I am working on, has to have User Authorization with roles, for simple users and for admins. With admin privileges I want to be able reset password for simple users or to add roles for them.
I was trying to apply Devise with cancancan gems, but unfortunately, couldn't make it work and I am not sure if that is even possible. So my question is which gems would you recommend to have such behavior. Or it's simpler to start from scratch?
Thank you for your answers.
Source: (StackOverflow)
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)
user
s can create new calendar
s.
- If a
user
is the owner
of a calendar
, then he can manage
the calendar
and all the administration
s 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 user
s 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?
Source: (StackOverflow)
I have models Owner
, Shop
and Item
.
Owner
has many Shops
and Shops
has many Items
.
My ability on Cancan:
can :manage, Shop, owner_id: user.id
can :manage, Item, shop: {owner_id: user.id}
When I open my rails_admin dashboard, it says that I have zero Items
and page List of Items
is empty.
However, when I open Shops page, I can see all its Items and I can change them on Shops page.
When I write my code like this:
can :manage, Item do |item|
item.shop.owner_id == user.id
end
It throws me an error:
The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for :index Item
Why I can't list all my Items
on Items List
?
Source: (StackOverflow)
I have CourseClass
model which has many AttendanceSheet
s and has_many
Teacher
s. I want the teacher(s) of a course_class instance to be able to manage that course_class's attendance sheets.
I'd expect the following to set that up:
if user.has_role? :teacher
can [:manage], AttendanceSheet, course_class: { teachers: {id: user.id } }
end
Users have a global roles, but also can have per-instance roles on a CourseClass. The above, in theory, should check they have the global role, and then the teachers
association finds only teachers with an instance role on that CourseClass.
In the controller @attendance_sheets is set by:
@attendance_sheets = @course_class.attendance_sheets.all
When I run ability checks I get the following results:
[1] pry(#<#<Class:0x007fb011970ea0>>)> can? :read, @attendance_sheets
=> false
[2] pry(#<#<Class:0x007fb011970ea0>>)> can? :read, AttendanceSheet
=> true
[3] pry(#<#<Class:0x007fb011970ea0>>)> can? :read, @course_class.attendance_sheets
=> false
[4] pry(#<#<Class:0x007fb011970ea0>>)> can? :manage, AttendanceSheet
=> true
I expected/want read
/manage
on AttendanceSheet
to be false
and :read
on @attendance_sheets
or @course_class.attendance_sheets
to be true
.
Thanks for any help in advance.
Source: (StackOverflow)
I am working on a Ruby on Rails project using ActiveAdmin and Cancancan. I defined some abilities for role users like super_administrator
, administrator
or subscribers
.
After writing some units tests I discovered than abilities where not working properly and I can't figured out what is wrong.
Concretely, I have a Newsletter module and I want only administrator
or super_administrator
to manage it.
Here is my ability excerpt:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # visitor user (not logged in)
alias_action :create, :read, :update, :destroy, to: :crud
if user.super_administrator?
# super_administrator privileges
elsif user.administrator?
# administrator privileges
elsif user.subscriber?
cannot :manage, Newsletter
else
cannot :destroy, :all
cannot :update, :all
cannot :create, :all
cannot :manage, Newsletter
end
end
end
My tests:
# this test breaks for no reason
test 'should not destroy newsletter if logged in as subscriber' do
sign_in @subscriber
assert_no_difference 'Newsletter.count' do
delete :destroy, id: @newsletter
end
assert_redirected_to admin_dashboard_path
end
private
def initialize_test
@newsletter = newsletters(:one)
@subscriber = users(:alice)
end
This test breaks because Newsletter is destroyed even if I wrote the ability for subscriber to not manage Newsletter.
What is weird as well is if I test abilities for subscriber, everything works:
# this test pass as expected by ability
test 'should test abilities for subscriber' do
sign_in @subscriber
ability = Ability.new(@subscriber)
assert ability.cannot?(:create, Newsletter.new), 'should not be able to create'
assert ability.cannot?(:read, Newsletter.new), 'should not be able to read'
assert ability.cannot?(:update, Newsletter.new), 'should not be able to update'
assert ability.cannot?(:destroy, Newsletter.new), 'should not be able to destroy'
end
I tried to manually test directly in browser and Abilities are not working either.
I don't understand what I missed. Does someone has any clue about what is wrong on my code ?
My Project:
- Ruby 2.2.2
- Rails 4.2.3
- ActiveAdmin 1.0.0 pre1
- Cancancan 1.12.0
Source: (StackOverflow)
I want to set the roles_mask
integer column to set the roles of a user
.
Here is my user model:
class User < ActiveRecord::Base
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
has_and_belongs_to_many :colleges
has_and_belongs_to_many :groups
has_and_belongs_to_many :pages
has_and_belongs_to_many :partners
ROLES = %w[admin college department news page ostad]
def roles=(roles)
roles = [*roles].map { |r| r.to_sym }
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r)}.inject(0, :+)
end
def roles
ROLES.reject do |r|
((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
end
end
scope :with_role, lambda { |role| {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0 "} }
def role_symboles
roles.map(&:to_sym)
end
def has_role?(role)
roles.include?(role)
end
end
And user_params
in my user controller:
def user_params
params.require(:user).permit(:name, :username, :roles, :email, :password, :password_confirmation, {:college_ids => [], :group_ids => [], :page_ids => [], :partner_ids => []})
end
And here is my form:
<%= form_for(@user) do |f| %>
....
<div class="row">
<div class="large-2 medium-2 small-12 columns">
<%= f.label t("admin.user.role") , class: "inline" %>
</div>
<div class="large-10 medium-10 small-12 columns">
<% for role in User::ROLES %>
<span style="width:40px">
<%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
<%= label_tag "user_roles_#{role}", role.to_s.humanize %>
</span>
<% end %>
<%= hidden_field_tag "user[roles][]", nil %>
</div>
</div>
<% end %>
And I have roles_mask
column in my user model.
But when I create a user, and set all the columns, roles_mask
column is still null
.
Source: (StackOverflow)
I have a problem that CanCan (in past and CanCanCan now) adds some strange SQL code to queries in tests.
My models:
class Company < ActiveRecord::Base
has_many :machines, dependent: :destroy
end
class Machine < ActiveRecord::Base
belongs_to :company
end
And I have CanCanCan abilities:
can :manage, :all
cannot [:manage, :read], Machine
can [:manage, :read], Machine, company_id: user.company_id
# other abilities also described as cannot / can pairs (legacy code)
If I run code snippet:
user.company_id
> 170
puts Machine.accessible_by(Ability.new(user)).to_sql
In development/production I have:
SELECT "machines".* FROM "machines" WHERE "company_id" = 170
In specs:
SELECT "machines".* FROM "machines" WHERE ('t'='f')
Other abilities works well (except models that belongs to machine).
Maybe I must add some other information - than ask please.
UPD: Adding :index to #accessible_by do not help:
puts Machine.accessible_by(Ability.new(user), :index).to_sql
CanCanCan v1.10.1
Source: (StackOverflow)
Example model
- Users are in 0..* car pools
- Each car is in 1 car pool
- Each car pickup has 1 car
Permission setup
can :read, Car, :car_pool => { :users => { :id => user.id } }
can :create, CarPickup
Use Case
The goal is to let the user create a new CarPickup for one of his cars. The view could look something along the lines of:
= form_for @car_pickup do
= f.association :car, :collection => Car.accessible_by(current_ability)
Question
When saving the user, it should be validated that the current user has access to the car passed with above form. Is there an elegant way of doing something along the lines of:
def create
@car_pickup = CarPickup.new(params[:car_pickup])
authorize :create, @car_pickup
if @car_pickup.car
Car.accessible_by(current_ability).include?(@car_pickup.car)
end
if @car_pickup.save
# ...
end
Solution attempts / further questions
Adapt abilities like so:
can :read, Car, :car_pool => { :users => { :id => user.id } }
can :create, CarPickup, :car => { :car_pool => { :users => { :id => user.id } } }
This breaks if the form is saved without selecting a product. For this solution, there would need to be a way to specify if not nil.
A second authorization call:
authorize! :read, @car_pickup.car if @car_pickup.car
Can can?(:read, @car_pickup)
be understood as the accessible_by
for a single record?
Could this probably be achieved using load_resource?
Source: (StackOverflow)
I have had Devise working in my rails4 app for quite some time already. Now I had a need to add different roles decided to use Cancan to accomplish this.
Everything was working just fine in development but when I deployed code to production all I got was infinite redirect loop. If I delete cookies I can get login page but signing in ends in the same loop.
My root redirects to controllers index action.
I have basically only one controller with any business logic and my root redirects to index action in this controller.
Related lines from the controller.
load_and_authorize_resource
skip_authorization_check :only => [:index]
But I have also tried this:
before_action :authenticate_user!
And in addition previous appended with both of these (separately)
:except => [:index]
:unless => :devise_controller?
In ApplicationController I have both all of these together and separately
check_authorization :unless => :devise_controller?
before_filter :authenticate_user!, :unless => :devise_controller?
And both of these I have tried with and without that unless statement.
I tried following relevant wikis to the letter but can't seem to get this working in production. If it makes any difference I am using Nginx/Passenger combo in production.
While writing this I realized that I have been using Cancan 1.6, I haven't tried Cancancan yet. Will give it a go next.
All help is appreaciated.
UPDATE: I tried this with Cancancan 1.9.2 (Instead of Cancan 1.6) with similar results. I have for the time being disabled it until I find a solution or alternative authorization gem.
Source: (StackOverflow)
I use the CanCanCan, Devise and Rolify gem to for Authentication and Permissionmanagement. But when I create a new controller I got this message:
NameError in PanelController#dashboard
uninitialized constant Panel
My PanelController:
class PanelController < ApplicationController
load_and_authorize_resource
def dashboard
end
end
When I remove this line: load_and_authorize_resource
the Route works. But I can access it without authentication. Do I need a PanelModel to use it?
My AbilityModel is this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
alias_action :create, :read, :update, :destroy, :to => :crud
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :user
can [:read], User
can [:update, :edit], User do |account|
account.email == user.email
end
else
# can :read, :all
can [:create, :new], User
end
end
end
Yesterday my Code works great but today I don't know why I get this error.
Maybe anyone can help me.
My Routes are this for the Controller:
devise_scope :user do
authenticated :user do
# Rails 4 users must specify the 'as' option to give it a unique name
root :to => "panels#dashboard", :as => :panel
end
unauthenticated do
root 'devise/sessions#new', as: :unauthenticated_root
end
end
Source: (StackOverflow)