Rails CANCAN - No Access on first click - ruby-on-rails

I have some trouble with Rails 4 and CanCan.
I did all like described here https://github.com/ryanb/cancan and actually it works but I have following problem:
Sometimes when I click on a link in the navi e.g. "Employee" to open the Employee/Show page CanCan fires an alert:
"...employee/?alert=You+are+not+authorized+to+access+this+page"
and I will redirected to the main page.
When I click again on the same link then page will open. No access problems now...
I dont know what the reason for this problem is... :(
I hope somebody can help.
Some Code:
ability.rb
def initialize(user)
if user.admin?
can :manage, :all
elsif user.secretary?
can :manage, :all
cannot [:destroy],[Employee, Setting, Section, Role, Position]
elsif user.leader?
can :manage, :all
cannot [:manage],[Setting, Section, Role, Position]
cannot [:destroy],[Project, Customer, Distributor]
cannot [:destroy, :edit],[Employee]
elsif user.employee?
can :manage, :all
cannot [:manage],[Setting, Section, Role, Position, Employee, Customer, Distributor]
cannot [:destroy, :edit],[Project]
else
#can :read, :all
end
end
employees_controller.rb
class EmployeesController < ApplicationController
before_action :set_employee, only: [:show, :edit, :update, :destroy]
#before_action :set_tmpPswVar, only: [:show]
#CanCan
load_and_authorize_resource
...
application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
include SessionsHelper
before_action :require_login
protect_from_forgery with: :exception
#load_and_authorize_resource
#CanCan
skip_authorization_check
#for CanCan version necessary because is not optimized for rails 4
#without that eacht create method will generate an ForbiddenAttributeError!
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
#CANCAN
rescue_from CanCan::AccessDenied do |exception|
redirect_to :controller=>"workdays", :action => "index", :alert => exception.message
end
...
Best regards
Kumaro

Unfortunately CanCan does not support Rails 4+. You should instead use CanCanCan:
https://github.com/CanCanCommunity/cancancan

Related

Devise admin routes

I have a devise User and inside I have admin as boolean default to false. How can I fixed my routes in my ruby on rails app for it to give access to certain pages ONLY for the admin who has admin as true.
UPDATE:
I changed followed the first answer I got which said to create a is_admin? method in my controller and specify what actions. However, when I do that, I get a:
undefined method `admin' for nil:NilClass
UPDATE 2:
Products Controller:
class ProductsController < ApplicationController
before_action :is_admin?, only: [:edit, :update, :destroy]
before_action :set_product, only: [:show]
Application Controller:
def is_admin?
if signed_in?
redirect_to root_path unless current_user.admin
end
end
You shouldn't do that in the routes file, the best place to do it's on the controller filtering part. Attention to the :authenticate_user! method being before the is_admin?. Otherwise current_user will be nil.
class PagesController < ApplicationController
before_action :authenticate_user!
before_action :is_admin?, only: [:action1, :action2]
...
private
def is_admin?
unless current_user.is_admin?
flash.alert = "Sorry, you don't have permissions to perform this action."
redirect_to root_path
end
end
end
I recommend you use pundit gem and policies for everything related to authorizations.

How can I block url adress in Devise (Ruby on Rails)

I blocked display links on the show page:
<% if #post.user == current_user %>
links
<%end%>
but I can't block url adress for unprivileged users:
http://localhost:3000/posts/1/edit
What can I do?
It's good possibility to use Pundit gem (https://github.com/elabs/pundit).
Your policy will look:
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def edit?
post.user == user
end
end
And your controller's action:
def edit
#post = Post.find_by(id: params[:id])
authorize #post
...
end
What you're looking for is something called authorization
Authentication = finding out if a user is present
Authorization =
determining if they are able to perform specific requests
The answer by Sergei Stralenia is correct - you'll need to use one of the authorization gems -- Pundit and CanCanCan being two of the most popular -- to validate whether a user is able to edit a particular object.
In regard the routing, you'll not be able to remove the edit route, unless you separate it out into something like an admin namespace (I'll explain more in a second).
--
Sergei Stralenia's post showed you how to use Pundit, I'll show you CanCanCan:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, Post
else
can :read, Post
end
end
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#article = Post.find params[:id]
authorize! :edit, #article
end
end
Admin
If you wanted to make a post only editable in an "admin" area, you'd be best using something like the following:
#config/routes.rb
resources :posts, only: [:index, :show]
namespace :admin do
resources :posts, only: [:new, :create, :edit, :update, :destroy]
end
This way, you will literally have no way for a non-admin user to edit/update posts in the front-end. Instead, they'll have to go into the admin area and make it so that they are able to edit it in there...
#app/controllers/admin/posts_controller.rb
class Admin::PostsController < ApplicationController
#actions & authorization in here
end
Within the edit action on your controller, perform the same check - something like:
#post = Post.find_by( id: params[:id] )
unless #post.user == current_user
fail(ActionController::RoutingError, 'User cannot edit this post.')
end
You can simplify the error check into:
fail(ActionController::RoutingError, 'User cannot edit this post.') unless #post.user == current_user
I hope this helps!
I guess the best way to do this is to use before_filter in your posts controller, i.e.:
before_action :authorize_admin, only: [:show, :edit, :update, :destroy]
or:
before_filter :authorize_admin, except: [:show]
where :authorize_admin is the method that You have to define either in posts controller (to use for posts only) or in application controller (to use in all controllers), like this:
def authorize_admin
redirect_to :new_user_session unless current_user&&current_user.admin?
end

authorise view user to not access some pages in rails4 app by using cancancan

Am using Devise for authentication and cancancan for authorization.Now i want to not allow the view user for some pages. So i add following codes. But it throws undefined local variable or method `current_user' for # error.
My ability.rb
class Ability
include CanCan::Ability
def initialize(dashboard_user)
current_dashboard_user ||= DashboardUser.new
if current_dashboard_user.CD_DASHBOARD_ADMIN?
can :manage, :all
else
can :read, :summary
can :read, :home
end
.........
end
Application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_dashboard_user!
protected
rescue_from CanCan::AccessDenied do |exception|
redirect_to main_app.root_url, :alert => exception.message
end
....
end
dashboard_user_controller.rb
class DashboardUsersController < ApplicationController
before_action :set_dashboard_user, only: [:show, :edit, :update, :destroy]
load_and_authorize_resource
....
end
Okey, just do this trick for now. Somehow the current_user helper method is being called. So the quickest solution would be if you can do the following.
In your application_controller.rb file put this block:
def current_user
current_dashboard_user
end
# method_alias :current_user=, current_user # you may need this line if required.
helper_method: :current_user
I hope this will help.

Understanding Cancan abilities

Trying to get Cancan securing a few models in an application and curious why it's not working the way I thought it would. I had thought you could can? on the specific instance as opposed to the entire class so, not in this example but, you could enable abilities on a per instance basis as a list of posts are displayed?!?
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? :moderator
can :manage, Post
else
can :read, :all
end
end
end
# posts/index.html.haml
...
- if can? :update, #post <- doesn't work
- if can? :update, Post <- works
Edit: add PostsController.rb
#posts_controller.rb
class PostsController < ApplicationController
before_filter :login_required, :except => [:index, :show]
load_and_authorize_resource :except => [:create]
def index
# #posts = Post.all ## <- handled by Cancan's load_and_authorize_resource
#events = Event.where("end_date <= :today", :today => Date.today)
#next_event = Event.next
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
...
end
This line:
- if can? :update, #post <- doesn't work
Is asking CanCan "can I update this specific post." You defined the ability in terms of all posts. If you had done:
can :update, Post, :user_id => user.id
Then your "if can?" would work, and the user would only be able to update their own posts. So you want to use the specific resource version ("#post") if something about this instance of the resource determines the permission, and you want to use the class version ("Post") if the user has the ability for all instances of the class.

How to obtain action level protection using Authlogic and STI?

Given that it is well-documented how to use before_filter for a single user classification, I'm having trouble getting action-level protection for multiple user types. Let me explain:
I've got something like this...
class ApplicationController < ActionController::Base
class << self
attr_accessor :standard_actions
end
#standard_actions = [:index, :show, :new, :edit, :create, :update, :destroy]
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
redirect_to home_url
return false
end
end
def require_admin
unless current_user and current_user.is_a?(Administrator)
store_location
redirect_to register_url
return false
end
end
end
And in the GuardiansController I want to only allow the standard actions for Administrator but all other actions should require Guardian. So I tried this...
class GuardiansController < ApplicationController
before_filter :require_admin, :only => ApplicationController::standard_actions
before_filter :require_guardian, :except => ApplicationController::standard_actions
...
end
Which ends up doing a recursive redirection. There must be a better way?
OK, this is another case of not looking carefully and missing something. I inadvertently had setup the route to redirect the user in a recursive way. The above solution works just fine when you set the routes properly:
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
# this route (home_url) sent the user to another controller which ran a before_filter sending them back here again.
# redirect_to home_url
# So I changed it to a neutral route and it works great!
redirect_to register_url
return false
end
end

Resources