Blocking Users From Specific Pages using Ruby on Rails and cancan - ruby

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

Related

Requiring Authy 2 factor authentication with Devise

I have an app set up with Devise log in and I want to implement two factor authentication with Authy/Twilio. I have it set up where if the user goes to the path /enable_authy they can get a text-code to verify their account. I'm trying to make it so it's required to do this, not just a bonus.
My routes...
devise_for :users,
:controllers => { :omniauth_callbacks => "users/omniauth_callbacks"},
:path_names => {
:verify_authy => "/verify-token",
:enable_authy => "/enable_authy",
:verify_authy_installation => "/verify-installation"
}
Twilio developer evangelist here.
There's no way with the gem itself to force a user to enable two factor authentication. You could, however, ensure this yourself with a before_action in your ApplicationController. You'd just need to check whether your signed in user had Authy enabled and redirect them to /enable_authy if they don't.
Something like:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :ensure_authy_enabled
private
def ensure_authy_enabled
return if params[:controller] == "devise/devise_authy"
if current_user and !current_user.authy_enabled?
redirect_to user_enable_authy_path
end
end
end
You might also want to set a flash message to explain what's happened or store the path the user was intending to visit so that you can redirect them there after they are set up with 2FA.
Let me know if that helps at all.

How are view helpers available in the controllers?

I have come across a bit of confusion regarding the use of view helpers controllers. The kind of scenario I have is:
session_helper.rb:
module SessionsHelper
# logs in the given user.
def log_in(user)
session[:user_id]=user.id
end
sessions_controller.rb:
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
end
end
def destroy
log_out
redirect_to root_url()
end
Now, as per the documentation that I have read, it mentions that helpers are used in views, to reduce the amount of coding to be done there.
My question is: how I am able to use the log_in and log_out methods defined in the session_helper in my controller?
If anyone can clear me on this concept would be very much helpful.
Answer on your question:
ActionController::Base.helpers.log_in(user)
But, it's better to place those methods in the controller.

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

Hide Products from list based on authorization using cancan in rails_admin

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

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