EzDevInfo.com

cancancan

Continuation of CanCan, the authorization Gem for Ruby on Rails.

Rails 4 + CanCanCan: "undefined method `role?' for User"

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 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)
    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)

How do I setup my CanCanCan permissions correctly?

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)

Advertisements

How to restrict user to search for a particular model in view?

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)

Nested routing and authorization using CanCanCan in Rails

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)

ActiveAdmin + CanCanCan errors with : protected method `authorize!' called for

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)

Rails 4 Authentication model with roles

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)

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.

  1. I had not added load_and_authorize_resource to the calendars and administrations controllers.

  2. 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)

Rails_admin, List of models show zero when using cancan

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)

Can't grant permission on class scoped to belongs_to association

I have CourseClass model which has many AttendanceSheets and has_many Teachers. 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)

Rails: Ability and ActiveAdmin not working as expected

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)

unset roles_mask column for cancancan gem in rails 4.2

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)

Wrong queries with CanCan / CanCanCan in specs

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)

CanCan: Check permission on record including dependency

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)

Devise + Cancan cause a redirection loop in production but not in development

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)

cancancan load_and_authorize_resource NameError

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)