I can't understand what I've missed.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Post
end
end
post_controller.rb
class PostController < ApplicationController
before_filter :authenticate_user!
def index
#posts = Post.all
authorize! :read, #posts
end
end
index.html.haml
- if can? :read, #posts
you can!
- else
you cannot!
Using this code, I always get CanCan::AccessDenied in PostController#index exception. It says there's something wrong at the line #8: authorize! :read, #posts
1.
If I change code in the post_controller.rb like this:
post_controller.rb
class PostController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
def index
#posts = Post.all
end
end
The exception is gone, but I get you cannot! from my view. I expect to get you can! message.
2. If I change can :read, Post to can :read, :all in the ability.rb, I get you can! message as expected. But that's not what I want to use.
What's wrong here?
Actually, either you use can :read, Post or you use can :read, post while looping #posts.
There is no in between.
btw, if you use load_and_authorize_resource, no need to add #posts = Post.all.
They are automatically loaded.
PS: why do you check in your controller AND in your view?
Related
In my app I have a model called User that has_one Talent.
In CanCanCan I have this ability:
class Ability
include CanCan::Ability
def initialize(user)
if user.nil?
can :read, User
can :read, Talent, is_public?: true
else
can :read, Talent, is_public?: true
end
My page is being rendered by the ProfilesController#show. Like this:
class ProfilesController < ApplicationController
before_action :check_ability, except: [:show]
def show
#user = User.find(params[:id])
authorize! :read, #user
authorize! :read, #user.talent
if current_user
sent_connections = current_user.sent_connections
connections = sent_connections + current_user.all_connections
#is_connected = !(connections.select { |c| c.user.id == #user.id }.empty?)
end
#top_5_photos = #user.top_5_photos
end
Well. Im trying to render a profile that the method: is_public returns false. But the page is being rendered correctly, while I expected was that the user cant see the page because of the rule:
can :read, Talent, is_public?: true
What Im missing here?
If I remember it correctly,
can :read, Talent, is_public?: true
^ is_public? above is expected to be an attribute by Cancancan.
But because is_public? is a custom method, then can you try the following instead?
can :read, Talent do |talent|
talent.is_public?
end
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&¤t_user.admin?
end
I want to disable access to a Pages controller for users having role "author", using cancan (by Ryan Bates).
The PagesController is as follows
class PagesController < ApplicationController
def new
#page = Page.new
authorize! :update, #page
...
end
...
end
This is returning uninitialized constant CanCan::Ability::I18n
Note that the same thing happens when I use
load_and_authorize_resource
filter instead of
authorize! :update, #page
I am using Rails 2.2.3.
Has anyone encountered a similar issue?
Thanks
Adding the ability.rb code:
class Ability
include CanCan::Ability
def initialize(current_user)
user = User.find(:first, :conditions => ["username = ?", current_user])
user ||= User.new # guest user
if user.role?('admin')
can :manage, :all
can :manage, WpArticle
elsif user.role?('moderator')
can :manage, :all
elsif user.role?('author')
can :create, WpArticle
can :update, WpArticle
can :read, WpArticle
end
end
end
You need to install the i18n gem. Once installing, it should hopefully work.
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.
I am using authlogic and cancan on a rails 3 application, I want to allow all logged in users to access the users index page, i have tried something like this but it dosent seem to be working:
ability class:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :index, User if UserSession.find
can :read, User if UserSession.find
end
Controller:
def index
#users = User.search(params[:search]).order('username').page(params[:page]).per(1)
authorize! :index, #users
end
def show
#user = User.find(params[:id])
authorize! :read, #user
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
thanks
I find it's easier to use load_and_authorize_resource at the top of my controllers. Then your ability class contains all the ability logic instead of having it strewn about your controllers.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user
can :index, User
can [:show, :edit, :update, :destroy], User, :id => user.id
end
end
end
users_controller.rb
class UsersController < ApplicationController
load_and_authorize_resource
def index
#users = User.search(params[:search]).order('username').page(params[:page]).per(1)
end
def show
end
...
end
I haven't used authlogic in a while as I tend to use devise now, so I'm not sure if my sample code is authlogic ready. If you don't want to use load_and_authorize_resource, my code shows how to limit what users can see in the ability class, but in your code I'd change :read to :show.
Continuing from my comment, the problem was in the following code
authorize! :index, #users
Here, you're passing an Array of users to the CanCan's method, while your can :index, User declaration defines the authorization for a User object.