many to many association with cancancan in rails - ruby-on-rails

My two controllers user and web_cred are associated through userPass .
I applied cancancan gem but still unable to create a new web_cred ability class.
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :manage, WebCred, user_passes: {user_id: user.id}
end
My controller
load_and_authorize_resource :user
load_and_authorize_resource :web_cred
def create
#web_cred = WebCred.new(web_cred_params) # Not the final implementation!
if #web_cred.save
userpass = UserPass.new
userpass.user_id = current_user.id
userpass.web_cred_id = #web_cred.id
userpass.save
redirect_to home_index_path
else
render new
end
end
Your answer will be appreciated.

Related

Cancancan Cant get it work with User and Admin

So i am using Cancancan gem in my application.I have Users authenticated with the Devise/Omniauth gems and Admins that they are authenticated with a simple custom authentication.
I want to achieve
ability.rb
def initialize(userOrAdmin)
if userOrAdmin.user?
can :read, User
return unless user.present?
can :manage, User, id: user.id
elsif userOrAdmin.admin?
can [:update, :read] , Admin, id: admin.id
end
end
end
but that doesnt work.
I tried to override the ability method like that
application_controller
def current_ability
if current_admin?
#current_ability ||= Ability.new(current_admin)
elsif current_user?
#current_ability ||= Ability.new(current_user)
end
end
but i am getting a nomethod current_admin error probably because Cancancan assumes a current_admin from device but cant find it although i am using an current_admin method of my own.
I also tried to assign roles with the enum in both User.rb and Admin.rb and change ability.rb properly but i got an undefined method admin? for User error
Cancancan verion 2.0
After some searching, I found some helpful articles:
CanCanCan: Defining Abilities - Best Practicies
CanCanCan: CCC That Scales
CanCanCan: Refactoring Abilities
Rails: Routing From The Outside In
I suggest you try the following:
# Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# Everyone:
can :read, User
# Users:
return unless user.present?
can :manage, User, user_id: user.id
# Admins:
return unless user.admin?
can :manage, :all
end
end
# Routes.rb
devise_for :users # current_user:
devise_for :admins # current_admin:
# Application_Controller.rb
def current_ability
#current_ability ||= current_admin ? AdminAbility.new(current_admin) : UserAbility.new(current_user)
end
I found a solution that works
application_controller.rb
def current_ability
if current_user
return if current_admin.present?
#current_ability ||= Ability.new(current_user)
elsif current_admin
return unless current_admin.present?
#current_ability ||= Ability.new(current_admin)
end
end
end
ability.rb
class Ability
include CanCan::Ability
def initialize(userOrAdmin)
if userOrAdmin.is_a? User
can :read, User
can [:update, :read], User, id: userOrAdmin.id
elsif userOrAdmin.is_a? Admin
can :read, Admin
can [:update, :read], Admin, id: userOrAdmin.id
end
end
end
Althought this works without errors . Whenever i am signed in both like a user and admin the admin role appears CanCan not authorized error .

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.

CANCAN undefined method `user_type?' for "admin":String

here is my problem:
I'm using cancan gem. I think all is set up well, except for one thing, that produces undefined method 'user_type?' for "admin":String.
Here is my ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.user_type? :admin
can :manage, :all
# [code code code code commented to find more easily the problem]
else
can :read, :all
end
end
end
My user.rb
require 'digest/sha2'
class User < ActiveRecord::Base
#definisco i ruoli
ROLES = %w[admin client banned]
default_scope :order => 'user' [...]
I can't figure out what I have to modify. Why if user.user_type? :admin don't check the current user attribute? Thanks a lot for your attention!
UP: Ok, I read around that cancan need method "current_user" declared in application controller. Now I work on it. News, soon.
You should do like this.I think it is better approach.
Seed roles into DB.
And in application helper paste this code.
def is_admin?(user)
admin_role = Role.find(:first, :conditions => ["name = ?", "admin"])
return user.roles.include?(admin_role)
end
And then in ability call like
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if is_admin?(user)
can :manage, :all
# [code code code code commented to find more easily the problem]
else
can :read, :all
end
end
end
And it will work fine.......

Resources