Understanding Cancan abilities - ruby-on-rails

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.

Related

How to write CanCanCan Ability for user to read only their data?

How do you restrict user access so a user can only read their own record?
I've tried:
def initialize(user)
can :read, User, :id => user.id
and this:
def initialize(user)
can :read, user
but I can still access every user in index and show. I have authorize_resource in the UsersController.
Relevant documentation for reference:
https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
:read != :show
:read == [:show, :index]
Unfortunately I don't have a setup to test it so its a shot in the dark.
can :show, User, :id => user.id
It seems that putting
authorize! :show, #user
in the show action and
#users = User.accessible_by(current_ability)
in the index action solves my issue using:
def initialize(user)
can :read, User, :id => user.id
I can now see I should have been using load_and_authorize_resource instead of just authorize_resource as it would have automatically added those.

Making a Moderator Authorized User role for my app

I have 2 types of roles for my user at the moment [:member , :admin], members can CRUD most post created by them . :admin can do CRUD any post period. Now im trying to create a moderator that can only View and update all posts. i have added :moderator to my enum role:. I also included
before_action :moderator_user, except: [:index, :show] and
def authorize_user
unless current_user.admin?
flash[:alert] = "You must be an admin to do that."
redirect_to topics_path
end
end
def moderator_user
unless current_user.moderator?
flash[:alert] = "You must be an moderator to do that."
redirect_to topics_path
end
end
but seem to be interfering with my before_action :authorize_user, except: [:index, :show] because it causes my rspec tests to fail.
Im trying to figure out how to create a moderator role which will be in between member and admin but without affecting either.
helper.rb :
def user_is_authorized_for_topics?
current_user && current_user.admin?
end
def user_is_moderator_for_topics?
current_user && current_user.moderator?
end
This is a perfect case for one of the authorization gems -- Pundit or CanCanCan. CanCanCan is probably the best for user-centric implementations...
#Gemfile
gem 'cancancan', '~> 1.13'
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in) #-> looks for "current_user"
case true
when user.admin?
can :manage, Post #-> CRUD all
when user.moderator?
can [:read, :update], Post #-> update/read posts
when user.member?
can :manage, Post, user_id: user.id #-> CRUD their posts
end
end
end
The above will give you the ability to use the can? and authorize methods in your controller & views:
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
load_and_authorize_resource
end
#app/views/articles/index.html.erb
<% #articles.each do |article| %>
<% if can? :update, article %>
<%= link_to "Edit", edit_article_path(article) %>
<% end %>
<% end %>
The above should do well for you.
The load_and_authorize_resource filter should provide you with scoped data:
As of 1.4 the index action will load the collection resource using accessible_by.
def index
# #products automatically set to Product.accessible_by(current_ability)
end
--
There is a great Railscast about this here. The creator of Railscasts authored CanCan before getting burned out, so a new community took it up with CanCanCan.

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

rails cancan gem uninitialized constant CanCan::Ability::I18n

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.

Rails & CanCan: If user is logged in then allow him/her to view index page?

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.

Resources