Ruby on Rails CanCan Gem - ruby-on-rails

I am a bit confused regarding CanCan Gem. I basically understand how to set up abillity.rb. For example lest say we have the following code:
// in abillity.rb
user ||= User.new
can [:update, :destroy, :edit, :read], Book do |book|
book.dashboard.user_id == user.id
end
And then lets say we have the following books controller:
// books_controller.rb
load_and_authorize_resource
def destroy
if can?(:destroy, #book)
#book.destroy!
redirect_to happy_world_path
else
redirect_to not_happy
end
end
My question is: Do we need to check 'can?(:destroy, #book)'?
From my understanding 'load_and_authorize_resource' will not even allow access to this method if we don't have abillity to destroy it.

Yo do not need to add if can?(:destroy, #book) in your action if you use load_and_authorize_resource
Like the README say
Setting this for every action can be tedious, therefore the load_and_authorize_resource method is provided to automatically authorize all actions in a RESTful style resource controller.
If an user without authorization try to destroy, he get a unauthorized response ( not remember if is a 401 code)
Maybe you can use if can?(:destroy, #book) in your views, to do no show thte destroy button. Like also in Check Abilities & Authorization section

Related

RoR: Devise enum roles, redirect after sign in based on role?

I have a user model with two roles as enums
enum role: [:'Standard', :'Admin']
I am trying to redirect based on the user role to relevant page after sign-in with Devise, I have used the recommended way of doing it on the docs.
In my sesssions controller...
def create
super
sign_out :user
end
def after_sign_in_path_for(_resource)
if resource.role == "Standard"
redirect_to dashboards_path
else
redirect_to dashboards_admin_index_path
end
end
And in my controller...
before_action :authenticate_salesperson!
before_action :set_project, only: %i[show edit update destroy]
I get this error saying too many renders/redirects (highlights super in create method) when logging in and i'm wondering why?
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return"
How to work around this? Ty.
Would be nice if you paste the whole controllers, but it seems like the after_sign_in_path_for method gets called before each time you visit either page, which creates a loop and hence the error. You can easily verify this by logging some text in each of the if else methods to double check.
What you should do is to add this logic to the controller, which is the root path like so
class DashboardController < ApplicationController
before_action: :after_sign_in_path_for, only: :index
private
def after_sign_in_path_for
if current_user.standard?
redirect_to dashboards_path
else
redirect_to dashboards_admin_index_path
end
end
end
Thanks. This works after I removed the "redirect_to's" in the "after_sign_in" method in my sessions controller
def after_sign_in_path_for(_resource)
if current_salesperson.standard?
dashboards_path
elsif current_salesperson.admin?
dashboards_admin_index_path
end
end
And in my user model...
def admin?
role == "Admin"
end
def standard?
role == "Standard"
end
You are redirecting too many times in the same action, this is why the message: "Render and/or redirect were called multiple times in this action."
Just return the path, delete redirect sentence! You only can redirect once in every action method!
Greetings

How to authorize namespace, model-less controllers using CanCanCan?

What is the correct way to authorize and check abilities for a namespaced, model-less controller using CanCanCan?
After much googling and reading the wiki, I currently have
#controllers/namespaces/unattacheds_controller.rb
def Namespaces::UnattachedsController
authorize_resource class: false
def create
# does some stuff
end
end
#models/ability.rb
def admin
can [:create], :namespaces_unattacheds
end
#view/
<%= if can? :create, :namespaces_unattacheds %>
# show a create form to authorized users
<% end %>
This is not correctly authorizing the controller. Admins can see the conditional create form, but are not authorized to post to the create action.
post :create, valid_params
Failure/Error: { it { expect( flash ).to have_content "Successfully created" }
expected to find text "Successfully created"
got: "You are not authorized to access this page."
In one example, the wiki suggests creating a separate Ability class for a namespaced controller. https://github.com/CanCanCommunity/cancancan/wiki/Admin-Namespace
Is there a simpler way to achieve this? This app uses many namespaced controllers, I don't really want to create an ability class for each one.
Is there correct syntax to refer to the namespaced controller in the Ability class?
can [:create], Namespaces::Unattacheds
can [:create], :namespaces_unattacheds
can [:create], namespaces/unattacheds
????
It sounds like you are setting permissions on the Namespaces::Unattacheds model, which means your controller doesn't need to do:
authorize_resource class: false
Your controller does have a model. Maybe it also inherits from ApplicationController? (That would be a logical thing to do.)
If you are trying to avoid affecting certain controller methods, use only/except clauses, as described here:
https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions#choosing-actions
I don't think the namespace depth is an issue IF it matches between your model and your controller. You just need load_and_authorize_resource and the proper form in ability.rb:
can [:create], Namespaces::Unattacheds
Maybe not the prettiest solution but I managed to achive this by adding
skip_authorization_check
before_action { raise CanCan::AccessDenied unless current_user.can?(params[:action].to_sym, ::namespaces_unattacheds) }
If you do it like this, you can pass whatever you want from this controller to the ability class.
You need to add the can? method first to be able to use this https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users

rails 4 user authentication

I'm using devise with my rails 4 app to handle the authentication, and no problems there.
However, I want to make sure a logged in user can only view / edit (via the show and update actions) the items that his user owns (that are linked to his user_id).
I think I could hack something to make this all work by checking the current_user.id, but many users in Stackoverflow and other places say to use cancan-- however it appears cancan is dead and gone, and there's a replacement called cancancan, which may be ok, but I don't know.
Is there a standard way to do this in Rails 4, or is the best route to still use a third party gem like cancancan? Is there a better gem?
I've been using Pundit instead of Cancan for the last few projects I've done. It is lightweight, flexible and easy to use. Here's the link: https://github.com/elabs/pundit
In regards to your question, you will create policies for each model. For each action you define a method. It's super simple and explained on the link I've attached. Here as an example you have update in your model (models/post.rb):
def update
#post = Post.find(params[:id])
authorize #post
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
Call authorize to define permissions.
In your policies/post.rb:
class PostPolicy < Struct.new(:user, :post)
def update?
user.admin? or not post.published?
end
end
That returns true or false. In your case if you want to check if the user is a owner you can place the following if statement:
if user.admin? || user.owner_of?(post)
You get the idea. You can also define scopes, etc.
I don't think there's a standard, per se, but rather it's based on what you need. For Rails 4, cancancan brings a lot to the table and is built off of a gem that has been used regularly by the Rails community.
The only other alternatives I'm familiar with are protector and pundit - maybe check those out.
However, if cancancan and protector don't fit your needs, you could always roll your own authorization solution, but to me, why reinvent the wheel if cancancan will satisfy your needs.
I'd recommend Action Access, it's much simpler and straightforward. It boils down to this:
class ArticlesController < ApplicationController
let :user, :all
let :guest, [:show, :index]
# ...
def edit
not_authorized! unless #article.user == current_user
# ...
end
# ...
end
First of this will automatically lock the controller, allowing only users to access every action, guests can only show or index articles. Then not_authorized! will reject and redirect with an alert any user other than the owner of the article.
What's good about this is that it makes controllers to be self contained, everything related to the controller is within the controller. This also makes it very modular and avoids leaving forgotten trash anywhere else when you refactor.
It's completely independent of the authentication system (so no problem with Devise) but it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :article %>
<%= link_to 'Edit article', edit_article_path(#article) %>
<% end %>
Here :article refers to ArticlesController, so the link will only be displayed if the current user is authorized to access the edit action in ArticlesController. It supports namespaces too.
You can lock controllers by default, customize the redirection path and the alert message, etc. Checkout the documentation for more.

How to secure user show page alongside user admin functions when using devise

I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.

Rails 3 authorization with default auth

I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end

Resources