CanCan: Load and Authorize some resources in a Controllers without models - ruby-on-rails

I've a controller without model. In this controller we are loading some other resources. Most importantly the application has multi-tenancy feature.
Here is the code:
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.owner?
can :manage, Tool, tenant_id: user.tenant.id
end
end
end
# boxes_controller.rb
class BoxesController < ApplicationController
authorize_resource class: false
def index
tools = Tool.all
end
end
What is the problem?:
Say, user1 of tenant1 creates tool1 and the user2 of tenant2 creates tool2.
The problem is, from tenant1, user1 can access tool2! :(
Did I wrote anything wrong? Please help.

Try this
if user.owner?
can :manage, Tool do |tools|
tools.tenant_id == user.tenant.id
end
end
and in controller
authorize_resource class: false
Hope that helps!

It is really bad habit of mine "not to read documentation of a gem completely". I've found solution here: https://github.com/ryanb/cancan/wiki/Fetching-Records
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.owner?
can :manage, Tool, tenant_id: user.tenant.id
end
end
end
# boxes_controller.rb
class BoxesController < ApplicationController
authorize_resource class: false
before_action :set_tool, only: %i(edit update destroy)
def index
tools = Tool.accessible_by(current_ability)
end
private
def set_tool
#tool = Tool.find params[:id]
authorize! :manage, #tool
end
end
Maybe it would help someone. Thanks!

Related

How do I get my ability.rb to work properly for my index action?

This is what my ability.rb looks like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
end
can :manage, Connection, inviter_user_id: user.id
end
end
In my controller I have this:
class ConnectionsController < ApplicationController
load_and_authorize_resource
skip_authorize_resource only: :index
layout 'connections'
def index
#family_tree = current_user.family_tree
#inviter_connections = current_user.inviter_connections.order("updated_at desc")
#invited_connections = current_user.invited_connections.order("updated_at desc")
end
end
In my application_controller.rb, I have this:
rescue_from CanCan::AccessDenied do |exception|
redirect_to authenticated_root_url, :alert => exception.message
end
Yet, when I try to visit /connections when I am not logged in, I get this error:
NoMethodError at /connections
undefined method `family_tree' for nil:NilClass
Also, when I remove the can :manage, Connection from my ability.rb it actually sends me to my login page like I expect.
How do I get both to work?
It looks like you are using Devise for authentication. For this kind of validation when using devise you should add this to your controller:
before_action :authenticate_user!
Try the following:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
else
can :manage, Connection, inviter_user_id: user.id
end
end
end
Also, noticed that you are skip_authorize_resource only: :index, try commenting out that and see if it works.
On line 8 of your controller, current_user is nil when you're not logged in, and it's calling family_tree on it.
You need something like (just as an example, it depends on your needs):
#family_tree = current_user.try(:family_tree) || FamilyTree.new
The reason it "works" when you remove the line in Ability is because that removes the ability to see the connection, so the before_filter redirects before you ever get inside index. What's probably tripping you up is the Connection record has a inviter_user_id of nil, and User#id is nil, so it's giving you permission to get into index.
It could also happen if you forgot to put this at the top of the controller:
load_and_authorize_resource
See docs for more.

Cancan ability definition: whole controller as an object

My question is absolutely theoretic, like "Is it right thing to do?".
I'm new to Rails in particular and to Ruby in general, and I'm trying to use Cancan autorization solution for my Rails appilcation.
Let's consider we have a simple contoller like this, a pair of associated views and an User model with DB table.
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
end
The goal is to restrict access to the "index" method to all but admins and permit regular users to see only their own pages, e.g. to permit user with id==5 to see page "users/5".
For this scope I've create an ability class for Cancan. Here it is:
class Ability
include CanCan::Ability
def initialize user, options = {}
default_rules
if user
admin_rules(user) if user.role.eql? "admin"
player_rules(user) if user.role.eql? "player"
end
end
def admin_rules user
can :read, UsersController
end
def player_rules user
can :read, User do |user_requested|
user_requested.id == user.id
end
end
def default_rules
end
end
My question is that:
Should I use UsersController as an object in "can" method if I do not have a handy one of type User? To applicate it later by "authorize! :show, UsersController" in the "index" method of the controller. Or it should be done in some other way?
Thank you for your suggestions.
No you don't want to add the UsersController to CanCan.
CanCan is meant to authorize resources, not Rails Controllers.
I would suggest the following:
def initialize(user)
if user.is_admin?
can :manage, User
else
can :manage, User, :id => user.id
end
end
This would allow the user only access to his own user unless he is an admin.
See the Defining abilities page in CanCan Wiki
I use a symbol, e.g., in the Ability class
def initialize(user)
if user.is_admin?
can :any, :admin
end
end
and in the controller
authorize! :any, :admin
In the wiki I found another way to set the ability. It's kind of advanced though, check it out here.
ApplicationController.subclasses.each do |controller|
if controller.respond_to?(:permission)
clazz, description = controller.permission
write_permission(clazz, "manage", description, "All operations")
controller.action_methods.each do |action|
...
+1 to #Tigraine.
Follow his instructions...
class Ability
include CanCan::Ability
def initialize user, options = {}
default_rules
if user
admin_rules(user) if user.role.eql? "admin"
player_rules(user) if user.role.eql? "player"
end
end
def admin_rules user
can :manage, User
end
def player_rules user
can :manage, User :id => user.id
end
def default_rules
end
end
and do this in your controller...
class UsersController < ApplicationController
load_and_authorize_resource
# => #users for index
# => #user for show
def index
end
def show
end
end
for details on load_and_authorize_resource see the bottom of this link

Passing params to CanCan in RoR

I have a controller with a method like;
def show
if params[:format].eql?("pdf")
// do something
elsif params[:format].eql?("csv")
// do something
end
end
But i have users with different roles. So i use CanCan to manage access control.
Now i want X role can do the action show in controller iff params[:format].eql?("csv")
I think it can be like ;can :show, resource if params[:format].eql?("csv"). So how can i send parameters to ability.rb?
Any idea?
Thanks.
In ApplicationController add the following:
# CanCan - pass params in to Ability
# https://github.com/ryanb/cancan/issues/133
def current_ability
#current_ability ||= Ability.new(current_user, params)
end
The most current answer is in the CanCan wiki: https://github.com/ryanb/cancan/wiki/Accessing-Request-Data
can takes two arguments: first is type of action that user is trying to perform on a resource, second is resource (can be class name or instance variable) itself. If you have your Ability set correctly, you should be able to do something like this:
def show
if params[:format].eql?("pdf")
// do something
elsif params[:format].eql?("csv")
if can? :read, resource
#do stuff
end
end
end
Don't forget that you have to have your user authenticated before running any CanCan checks.
can? method only returns true or false. I normally like to use authorize! method to check abilities. Unlike can, it would rise CanCan::AccessDenied error that you can rescue and process gracefully. Something in the lines of:
#models/ability.rb
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? :hiring_manager
can [:read, :update], Post, user_id: user.id
end
end
end
#controllers/posts_controller.rb
class PostsController < ApplicationController::Base
before_filter :authenticate_user
def show
#post = Post.find(params[:id])
authorize! :read, #post # will thorow an exception if not allowed
end
end
Then, I just catch the exception on ApplicationController level.

Rails 3 If Statement using a variable from a controller to another controller (ability.rb)

I'm using the Rails Plugin CanCan to handle permissions checks.
I have the following in ability.rb:
def initialize(user, projectid_viewing)
user ||= User.new
if projectid_viewing == 8
can :manage, :all
else
can :read, :all
end
end
The projectid_viewing is being sent from:
class ProjectsController < ApplicationController
before_filter :prepareCanCan, :only => [:show, :edit]
def prepareCanCan
#project = Project.find(params[:id])
projectid_viewing = #project.id
end
I have the 8 hardcoded above for testing purposes. and for some reason it isn't working at the if statement, did I do that statement incorrectly? It's always allowing for can: manage
I have the Project's controller logging, so I know that the value the controller is setting to projectid_viewing is 8.
Ideas?
I'm trying to understand… Depending on the project, all user can edit it, or can edit all models? If it's the project only, I would try:
def initialize(user)
user ||= User.new
can :manage, Project do |project|
project.id == 8
end
end

devise+cancan not blocking access to index problem wherer #proj = Proj.all

I have an app that uses Devise and CanCan.
in the config>initializers>Abiliity.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.is? :superadmin
can :manage, :all
elsif user.is? :user
can :read, Project do |project|
project && project.users.include?(user)
end
end
end
end
I have problem with the index action of Project controller, the project controller is a normal stock RESTful controller. Basically, a user who's a normal user, when logged in, can see the projects#index. But not all projects have this user as 'normal user', why isn't cancan blocking his access?
Thanks
Make sure you're calling load_and_authorize_resource in your ProjectsController, along the lines of:
class ProjectsController < ApplicationController
load_and_authorize_resource
#...
end
If that still doesn't work, try calling the authorize! method inside the index action, to see if that makes a difference, eg:
class ProjectsController < ApplicationController
#...
def index
#projects = Project.all
authorize! :read, #projects
end
#...
end

Resources