EzDevInfo.com

cancan

Authorization Gem for Ruby on Rails.

Testing views that use CanCan and Devise with RSpec

I was trying to test a simple index view, which has following code inside:

- if can? :destroy, MyModel
  %th Options

MyModelsController has following options (Inherited Resources + CanCan + Devise):

class MyModelsController < ApplicationController
  inherit_resources
  nested_belongs_to :mymodel
  before_filter :authenticate_user!
  load_and_authorize_resource :project
  load_and_authorize_resource :mymodel, :through => :project

When running specs, it crashes at the line - if can? :destroy, MyModel

Failure/Error: render
   ActionView::Template::Error:
      undefined method `authenticate' for nil:NilClass

There's no traceback, nothing to base on...

I thought that maybe I'm not authorized and signed when testing views, but Devise::TestHelpers should only be included in controller tests (and that's how I have it).

I was trying to override method can? in both Ability and the controller, but that gave no effect.


Source: (StackOverflow)

CanCan: limiting a user's ability to set certain model attributes based on their role

I have a Post model with a :published attribute (boolean) and a User model with a role attribute (string). There are three roles: ROLES = %w[admin publisher author]

I don't want users whose role is author to be capable of setting, or editing, the :published field on the Post model.

I'm using CanCan (and RailsAdmin gem) and my simplified Ability.rb file looks like this:

class Ability
  include CanCan::Ability
  def initialize(user)
    user ||= User.new

    if user.role? :admin
      can :manage, :all
    elsif user.role? :publisher
      can :manage, Post
    elsif user.role? :author
      # I want to prevent these guys from setting the :published attribute
    end

  end
end

Anyone got any tips for doing this sort of thing?


Source: (StackOverflow)

Advertisements

CanCan load_and_authorize_resource triggers Forbidden Attributes

I have a standard RESTful controller that uses strong parameters.

class UsersController < ApplicationController
  respond_to :html, :js

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def edit
    @user = User.find(params[:id])
  end

  def create
    @user = User.new(safe_params)

    if @user.save
      redirect_to @user, notice: t('users.controller.create.success')
    else
      render :new
    end
  end

  def update
    @user = User.find(params[:id])

    if @user.update_attributes(safe_params)
      redirect_to @user, notice: t('users.controller.update.success')
    else
      render :edit
    end
  end

  def destroy
    @user = User.find(params[:id])

    if current_user != @user
      @user.destroy
    else
      flash[:error] = t('users.controller.destroy.prevent_self_destroy')
    end
    redirect_to users_url
  end

  private

  def safe_params
    safe_attributes =
      [
        :first_name,
        :last_name,
        :email,
        :password,
        :password_confirmation,
      ]
    if current_user.is?(:admin)
      safe_attributes += [:role_ids]
    end
    params.require(:user).permit(*safe_attributes)
  end
end

In my config/initializers I have the file strong_parameters.rb

ActiveRecord::Base.send(:include,  ActiveModel::ForbiddenAttributesProtection)

When I add a simple call to CanCan's load_and_authorize_resource I get

1) UsersController POST create with invalid params re-renders the 'new' template
 Failure/Error: post :create, user: @attr
 ActiveModel::ForbiddenAttributes:
   ActiveModel::ForbiddenAttributes
 # ./spec/controllers/users_controller_spec.rb:128:in `block (4 levels) in <top (required)>'

Where @attr in the test is defined as

  before(:each) do
    @attr =
      {
        first_name: "John",
        last_name: "Doe",
        email: "user@example.com",
        password: "foobar",
        password_confirmation: "foobar"
      }
  end

In the tests I have it all setup properly to login the user and give them the necessary roles for being an administrator so I know it's not that. I don't know why this is causing ForbiddenAttributes to trigger. I'm sure it's something simple I've overlooked. Has anyone else encountered this problem and found a solution to it?


Source: (StackOverflow)

Rails4 authorization strategies

When it comes to Authorization/Authentication devise + cancan are usually my gems of choice. After the release of Rails4's strong parameters I've been looking into using the cancan_strong_parameters gem.

I can't shake the feeling that this approach seems a bit 'hacky'. The other options seems to be TheRole gem or simply rolling my own auth from scratch.

Was hoping anyone with first hand experience here could give a few pointers on how they tackled the problem, what problems the faced and where each approach fell short (if anywhere).

I know this isn't a clean cut StackOverflow typed question, but there doesn't seem to be much info regarding this subject when Googling. Thanks.


Source: (StackOverflow)

Context aware authorization using CanCan

I want to use CanCan to handle my permissions. My site has many different permissions levels, and most of them are context aware. For instance, Here are the relations in my 3 main models:

class User < ActiveRecord::Base
  has_many :league_relations
  has_many :leagues, :through => :league_relations
end

class League < ActiveRecord::Base
  has_many :league_relations
  has_many :users, :through => :league_relations
end

class LeagueRelation < ActiveRecord::Base
  belongs_to :user
  belongs_to :league
end

Note, LeagueRelations is a nested resource of Leagues. What I want to do is allow a user to modify leagues, and gauge each user's authorization based off of data stored in league_relation. I would then like a user to modify league relation, based only the data stored in the user model.

To be succinct: I basically want LeagueRelations to be used to authorize League actions, and Users to be used to authorize LeagueRelations actions. i.e. league_relation.owner = true to delete a League, but user.owner? must be true to delete a LeagueRelation. How can I authorize based on the attributes of league_relation when inside the league controller, and authorize other actions in other controllers on other models. Please leave a comment if you need more clarification.

Thanks.


Source: (StackOverflow)

CanCan - Access denied - Way to make CanCan Specify in the LOG Why?

I'm working to implement CanCan. For some reason CanCan keeps giving me Access Denied when I try to get specific about model permissions. And I can't figure out why.

Is there a way to get CanCan to be specific, perhaps in the logs or in development about Why Access is denied? something like, No Read Ability to XXX Model.

That would be helpful for debugging.

Thanks


Source: (StackOverflow)

Why is this rspec request spec not updating the model?

I have a requests spec for interactions with the User model. I want to make sure that Users with the Admin role can create/edit/destroy Users. I'm having a problem right now where the Edit action does not update the user. Everything works properly when I manually go through the actions on the site itself, but the tests fail to update the user.

Here's my spec:

it 'edits a user' do
  @user = FactoryGirl.create(:user)
  visit new_user_session_path unless current_path == new_user_session_path
  fill_in "Email", :with => @user.email
  fill_in "Password", :with => @user.password
  click_button "Sign In"
  user_to_edit = FactoryGirl.create(:user, first_name: "John", last_name: "Smith")
  visit edit_user_path(user_to_edit) unless current_path == edit_user_path(user_to_edit)
  fill_in 'user_last_name', with: "Changed"
  expect{
    click_button "Do it"
  }.to change { user_to_edit.last_name }.from("Smith").to("Changed")
  page.should have_content "John Changed"
end

The error that I get is:

Failure/Error: expect{
       result should have been changed to "Changed", but is now "Smith"

If I change the last few lines of the test to this:

  fill_in 'user_last_name', with: "Changed"
  click_button "Do it"
  page.should have_content "John Changed"

Then the test succeeds. This doesn't seem right, since the page should not display "John Changed" if user_to_edit was not updated.

My Delete request spec works fine:

it "deletes a user" do
  @user = FactoryGirl.create(:user)
  visit new_user_session_path unless current_path == new_user_session_path
  fill_in "Email", :with => @user.email
  fill_in "Password", :with => @user.password
  click_button "Sign In"
  user_to_delete = FactoryGirl.create(:user, first_name: "John", last_name: "Smith")
  visit users_path unless current_path == users_path
  expect{
    within ".user_#{user_to_delete.id}" do
      click_link 'Delete'
    end
  }.to change(User,:count).by(-1)
  page.should_not have_content "John Smith"
end

I have a user model:

class User < ActiveRecord::Base
  ROLES = %w[renter landlord admin]
  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
  attr_accessible :email, :password, :password_confirmation :first_name, :last_name, :role

  validates :password, :presence => true, :on => :create
  validates :first_name, :presence => true
  validates :last_name, :presence => true

  before_save :set_phones

  def set_phones
    self.fax = Phoner::Phone.parse(self.fax).format("%a%n") unless self.fax.blank?
    self.land_phone = Phoner::Phone.parse(self.land_phone).format("%a%n") unless land_phone.blank?
    self.mobile_phone = Phoner::Phone.parse(self.mobile_phone).format("%a%n") unless mobile_phone.blank?
  end
end

I have this factory:

require 'faker'

FactoryGirl.define do
  factory :user do |f|
    f.first_name { Faker::Name.first_name }
    f.last_name { Faker::Name.last_name }
    f.email {Faker::Internet.email}
    f.password { "oq2847hrowihgfoigq278o4r7qgo4" }
    f.role { "admin" }
  end
end

I have these actions in my user controller:

  def edit
    @user = User.find_by_id(params[:id])

    respond_to do |format|
      format.html
    end
  end

  def update
    if params[:user][:password].blank?
      [:password,:password_confirmation].collect{|p| params[:user].delete(p) }
    end

    respond_to do |format|
      if @user.errors[:base].empty? and @user.update_attributes(params[:user])
        flash.now[:notice] = "Your account has been updated"
        format.html { render :action => :show }
      else
        format.html { render :action => :edit, :status => :unprocessable_entity }
      end
    end
  end

The routes.rb file is also relevant, since I'm using Devise and have a custom Users Controller:

  devise_for :users, :skip => [:sessions, :registrations]

  devise_scope :user do
    get "login" => "devise/sessions#new", :as => :new_user_session
    post 'login' => 'devise/sessions#create', :as => :user_session
    delete "logout" => "devise/sessions#destroy", :as => :destroy_user_session
    get "signup" => "devise/registrations#new", :as => :new_user_registration
    put "update-registration" => "devise/registrations#update", :as => :update_user_registration
    delete "delete-registration" => "devise/registrations#destroy", :as => :delete_user_registration
    get "edit-registration" => "devise/registrations#edit", :as => :edit_user_registration
    get "cancel-registration" => "devise/registrations#cancel", :as => :cancel_user_registration
    post "create-registration" => "devise/registrations#create", :as => :user_registration
  end

  resources :users, :controller => "users"

Source: (StackOverflow)

Admin Authorization w/ CanCan

A have a bunch of controllers w/ the Admin namespace. I want to restrict access to these unless the user is an admin. Is there a way to do this using CanCan without having to call unautorized! in every method of every controller?


Source: (StackOverflow)

Serialize permissions (e.g. CanCan) with active_model_serializers

How do I serialize permissions with active_model_serializers? I don't have access to current_user or the can? method in models and serializers.


Source: (StackOverflow)

How can I use RSpec to test the response code on a CanCan failed authorization?

I'm working on a rails project in which I use CanCan to authorize my resources. When a user is not signed in and tries to submit a "talk" (via an ajax form submission), CanCan correctly raises a 401 with {"status":"error","message":"You must be logged in to do that!"} as the response (I have verified this in the browser using firebug). However, in my tests get a 302 response code rather than a 401:

class TalksController < ApplicationController
  authorize_resource

  def create
    @talk = current_user.talks.build(params[:talk])

    respond_to do |format|
      if @talk.save
        response = { :redirect => talk_path(@talk) }
        format.html { redirect_to @talk, notice: 'Talk was successfully created.' }
        format.json { render json: response, status: :created,  }
      else
        format.html { render action: "new" }
        format.json { render json: @talk.errors, status: :unprocessable_entity }
      end
    end
  end
end

talks_controller_spec.rb:

describe TalksController do
  describe "POST create" do
    context "when not signed in" do
      it "should not assign talk" do
        post :create
        assigns[:talk].should be_nil
      end
      it "should respond with a 401" do
        post :create
        response.response_code.should == 401
      end
    end
  end
end

The first example included here is successful (assigns[:talk] does not get assigned), but the second is not:

1) TalksController POST create when not signed in should respond with a 401
     Failure/Error: response.response_code.should == 401
       expected: 401
            got: 302 (using ==)
     # ./spec/controllers/talks_controller_spec.rb:53:in `block (4 levels) in <top (required)>'

I'm not sure exactly what's going on. Is there a way I can test for the actual response code returned to the browser? Or a better way I can test the authorization?


Source: (StackOverflow)

Rails Can Can Ability Class For Multiple Devise Models

I was wondering how I can define an ability class and serve that ability class depending on the user that has logged in.

I am using Active Admin, Can Can and Devise and I have successfully created a User and an AdminUser models.

I have this in my ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if (user)
      can :manage, Item
    end
  end
end

Now I have used this wiki entry to determine that we can indeed define a custom ability file and use that instead of the ability.rb:

https://github.com/ryanb/cancan/wiki/changing-defaults

But what I wanted to do is, be able to use ability.rb if a "non-admin user" is signed in and a custom abilty if a user admin is signed in.

Side Question: Could it be done such that I don't need a custom one and I could set permissions in one ability.rb file?


Source: (StackOverflow)

Using cancan to prevent access to controller

I have an admin controller and I want that only users that are defined as admin would have access to that controller.

my ability class:

class Ability
  include CanCan::Ability

  def initialize(user)
    if user.admin?
      can :manage, :all
    else
      can :read, :all
    end
  end
end

my admin controller:

class AdminController < ApplicationController
  load_and_authorize_resource

  def index
  end

  def users_list
  end
end

when i try to access /admin/users_list (either with an admin user or without) i get the following error: uninitialized constant Admin

What am I doing wrong? Is that the right way to restrict access to a controller?


Source: (StackOverflow)

CanCan difference between :read and [:index, :show]?

According to all documentation, the :read action is aliased to both :index and :show:

alias_action :index, show, :to => :read

However, consider the following scenario with nested resources:

resources :posts
  resources :comments
end

If I define abilities like this:

# ability.rb
can :read, Post
can :show, Comment

# comments_controller.rb
load_and_authorize_resource :organization, :find_by => :permalink
load_and_authorize_resource :membership, :through => :organization

things work as expected. However, if I change the :read action to [:index, :show]:

# ability.rb
can [:index, :show], Post
can :show, Comment

# comments_controller.rb
load_and_authorize_resource :organization, :find_by => :permalink
load_and_authorize_resource :membership, :through => :organization

I am unauthorized to access /posts/:post_id/comments, /posts/:post_id/comments/:id, etc. I still, however, can access both :index and :show for the posts_controller.

How is possible that these actions are "aliased", if they behave differently?

In my fiddling, I also came across the following. Changing load_and_authorize_resource to the following allowed access:

# ability.rb
can [:index, :show], Post
can :show, Comment

# comments_controller.rb
load__resource :organization, :find_by => :permalink
load_and_authorize_resource :membership, :through => :organization

Can someone explain what's going on here?


Source: (StackOverflow)

What is current_ability in cancan's accessible_by (fetching records)?

In the documentation of cancan it shows how to fetch all accessible records (in http://wiki.github.com/ryanb/cancan/fetching-records) in this way:

@articles = Article.accessible_by(current_ability)

but what is current_ability? I've tried passing the current user which I'm using for authentication and authorization, but I've got this error:

NoMethodError: undefined method `conditions' for #<User:0x1092a3b90>

Any ideas what should I pass to accessible_by or what's wrong here?


Source: (StackOverflow)

RSpec authorization testing with raise_error not working

I'm trying to test how a not logged in user behaves like this

  describe "not logged in user" do
   user_no_rights

    it "can't access action index" do
      expect(get :index).to raise_error(CanCan::AccessDenied)
    end
  end

The output when i run rspec

  Failure/Error: expect(get :index).to raise_error("CanCan::AccessDenied:You are not authorized to access this page.")
     CanCan::AccessDenied:
       You are not authorized to access this page.

So it looks like the correct execption is raised, but why is the spec not passing?


Source: (StackOverflow)