Hide Products from list based on authorization using cancan in rails_admin - ruby

I have a Product model.
Ability class is as follows:
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
else
can :read, Product, Product.all.limit(10) if user.has_role? :brand_manager
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
Here is the code for rails_admin:
RailsAdmin.config do |config|
config.authorize_with :cancan
end
I want to hide all the products from list which don't have particular name? Some how rails_admin does not support it. Can anyone help me to resolve this issue?

You can do something like this.
1. Create a new field in model like admin_user_id
2. While creating/updating product from save current admin user details in product data
and use it ability class
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
elsif user.has_role? :brand_manager
can :manage,Product, :admin_user_id=> user.id
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end

What do you mean with hide all the producs from the list ?
If you want that users can't read it, you can try:
can :read, Product do |product|
[name1, name2].include?( product.name )
end
If you want don't return these products, you can use in your controller:
Product.where( :name.in => [name1, name2] )
I hope I've helped

Related

Ruby on Rails: Having problems selecting correct parameters for a method depending on context the is method used

I am working on a application where Users can list their in-game items to trade with other Users. A user's profile url would be something like this:
/users/1/index
And their user listings profile would be something like
/users/1/listings/1
All other resources nested under users would be the same as the latter.
I am trying to implement a method that is called by a before_filter callback that checks to see if a user has blocked or is blocked by the user who owns the profile and respective nested resources such as ability to message them, view their listings etc. If either has blocked each other, then they redirected to the root page of the application. This is the method that I use for the before_filter:
def blocked_relationships
if blocked?
redirect_to :root
end
end
I used another method that checks the state of the relationships between the two users.
This is the method I found and worked on after some research courtesy of the Rails Recipes book:
def blocked?
Relationship.exists?(user_id: current_user.id, other_user_id: params[:user_id], status: "blocked") ||
Relationship.exists?(user_id: params[:user_id], other_user_id: current_user.id, status: "blocked")
end
The problem I have is that this method only works, for example, when User 1 is looking at User 2's items, messages, listings etc. because the url:
/users/2/listings [or items or etc]
will contain a params that makes reference to the user as params[:user_id]. params[:id] in this case and context will refer to the listings id.
BUT, if I am User 1 and I have blocked User 2 and visit User 2's profile, this method will not work because the url /users/2/index will use params[:id] to instead of params[:user_id].
I've been thinking about how to implement this in a DRY way but I can't seem to solve my problem other than doing something like this:
def blocked?
if params[:user_id].blank?
Relationship.exists?(user_id: current_user.id, other_user_id: params[:id], status: "blocked") ||
Relationship.exists?(user_id: params[:id], other_user_id: current_user.id, status: "blocked")
else
Relationship.exists?(user_id: current_user.id, other_user_id: params[:user_id], status: "blocked") ||
Relationship.exists?(user_id: params[:user_id], other_user_id: current_user.id, status: "blocked")
end
end
I also considered the possibility that I'm not even implementing my blocking feature correctly, but before I address that issue, I was wondering if anyone had any ideas on how to solve this problem. Any help or feedback would be greatly appreciated and I would be happy to add anymore information for clarification. Thanks!
Why not other_id = params[:user_id] || params[:id]? This is a way to override :id when :user_id is present.
About your blocking feature though, to me I'd like to see a user even if I've blocked them. I'd create a blocked_by_user_id field on the Relationship to see who did the blocking and only disallow the blocked party from seeing the user's profile.
You'd probably want to checkout authorization gems for rails like cancan or related (it's not my favorite but the most popular). However, you could handle it like this:
class User
has_many :relationships,
scope :accessible_by,
->(user) { where.not id: user.relationships.where(status: :blocked).pluck(:other_user_id) }
end
Then use the relationship User.accessible_by(current_user) on your controller instead of plainly User to retrieve resources. For example:
class UsersController < ApplicationController
def index
#users = User.accessible_by(current_user)
# bleh
end
def show
#user = User.accessible_by(current_user).find(params[:id])
# etc
end
end
When the resource is nested under a user you could do this:
class Users::PicturesController < UsersController
def index
#pictures = User.accessible_by(current_user)
.find(params[:user_id]).pictures
end
def show
#picture = User.accessible_by(current_user)
.find(params[:user_id]).pictures.find(params[:id])
end
end
When a user tries to access a resource that can't view, ActiveRecord::RecordNotFound will be raised, so you should handle it:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNorFound, with: :rescue_not_found
private
def rescue_not_found
redirect_to root_path,
notice: 'You can\'t access that with your current priveleges. '
end
end

Blocking Users From Specific Pages using Ruby on Rails and cancan

I am learning Ruby on Rails and was looking into utilizing cancan to help restrict users access to actions that they shouldn't have and to pages depending on who they are. I currently understand how to restrict actions, but I was curious if someone could help with actually restricting certain pages and unique pages.
One example is I have a home page for admin users and one for regular users, how would I restrict the admin page from the normal user?
Thanks, and any pointers on if I am doing something wrong is greatly appreciated.
If you want to use cancan :
Admit you add in your user controller a method admin_home :
def admin_home
#user = current_user
authorize! :admin_home
end
You need to specify in ability.rb file you want to restrict access to admin_home for standard users :
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
#Authorize all actions
can :manage, User
else
#authorize only self modifications and restrict access to admin_home
can :manage, User, :id => user.id
cannot :admin_home, User
end
end
end
You can find great resources about cancan in official wiki like
https://github.com/ryanb/cancan/wiki/Defining-Abilities and
https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions
Hope this help
Note: I am just giving you an example, you are not supposed to use it as it is, but you can have an Idea that how you will be able to put your logic.
class AdminsController < ApplicationController
before_filter :check_admin, :only => [:index, :show]
def index
#admins = //whatever your query for this action
end
def show
#admin = //whatever your query for this action
end
protected
def check_admin
if(my_condition to check if user type is admin)
{
return true // or anything u want for ur admin user
}
else
{
//anything here when user is not admin
1. you can redirect to users home page using redirect_to
2. you can redirect to a specific page which shows "You are not authorized to see this web page"
}
end
end
end

How to define conditional abilities using cancan

I am using cancan gem for authorization. I have defined ability.rb module to initialize roles and their permissions for different modules.
`def initialize(user)
user ||= User.new # guest user (not logged in)
members = user.members
members.each do |member|
role = member.role
if role.presence
permissions = role.permissions
permissions.each do |permission|
actions = Array.new
actions = permission.activity_name.split(",") if permission.activity_name.presence
app_module = permission.application_module
actions.each do |action|
if app_module.module_name == "Project"
Rails.logger.debug "in if"
can action.downcase.to_sym, app_module.module_name.constantize, :id => member.project_id if action.presence
elsif app_module.module_name == "Sequence"
Rails.logger.debug "in sequence and project id is #{member.project_id} and action is #{action}"
can action.downcase.to_sym, app_module.module_name.constantize, :project_id => 0..1 if action.presence
else
Rails.logger.debug "Module name is #{app_module.module_name}"
can action.downcase.to_sym, app_module.module_name.constantize if action.presence
end
end
end
end
end
end`
Here user has assigned a project and project has many sequences.
If user has assigned a project with id 1 then I have to fetch only those sequences whose project id is 1
In controller I have written -
load_and_authorize_resource
The permissions for project controller is properly fetched but for sequence controller if I pass project id also it was not restricting to other project.
Can anyone please help me
Conditional abilities would be in your ability.rb file.
You would do something like this in your ability.rb file:
can [:<actions>], Sequence do |sequence|
sequence.project.user_id == user.id
end

Rails, CanCan manage all & other roles

class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
can :manage, :all
can :custom_action, User, role: 'admin'
end
end
and in view
if can? :custom_action, #user
SHOW SOMETHING
this if always show "SHOW SOMETHING", don't understood why it's happend.
Well, that's because in your ability class, you give every user all rights.
You are probably looking for something like this:
def initialize(user)
#user = user || User.new
can :manage, :all
# When user is an admin, grant her extra privileges
if #user.is_admin?
can :custom_action
end
end
This way, you define the abilities (by using can) conditionally
Solution is:
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
can :manage, :all
cannot :custom_action, User, role: 'admin'
end
end
In view:
if can? :custom_action, #user
return
user = true
admin = false
This is not perfect solution but it's works

How to let Users edit a resource they create but not others when resource doesn't belong to User?

In my application using CanCan I have permissions where users can view and create stores but I also want them to only be able to edit the ones they've created. Users can create as many stores as they like, which all should be editable by them. A store doesn't have users so how could I do this when theirs no user_id apart of the Store table?
CanCan:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role == "default"
can :read, Store
can :create, Store
end
end
end
Since a user will be able to create as many stores as they like, a store will belong to a user.
You MUST create this relationship.
So, in the User model.
class User < ActiveRecord::Base
has_many :stores
end
And in the Store model.
class Store < ActiveRecord::Base
belongs_to :user
end
And in the ability.rb file, just put something like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role == 'default'
can :manage, Store , :user_id => user.id
end
end
end
I would add the following to the store model:
has_one :created_by, :class => User
Then add a migration to add a created_by_id to your Store class.
You should then be able to add a CanCan::Ability:
can :edit, Store, :created_by => user
I agree with a previous poster, that you must set up a relationship between User and Store. The relationship can be one-to-many (as Kleber S. showed), or many-to-many, if a store can have multiple users.
Then, the best way to handle access control is in the controller, by using the association. For the show, edit, update, destroy methods, you'll need to find the store, given a logged in user, so do something like this:
class StoresController < ApplicationController
before_filter :find_store, only: [:show, :edit, :update, :destroy]
def show
end
def edit
end
def update
if #store.update_attributes(params[:store])
# redirect to show
else
# re-render edit, now with errors
end
end
# ...
private
def find_store
#store = current_user.stores.find(params[:id])
end
end
This way, the association takes care of limiting the lookup to only those stores that are connected to the current_user by foreign key. This is the standard way for RESTful Rails resources to perform access control of associated resources.

Resources