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
Related
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
I have a small code organization problem in a Rails 4 app.
Let's say that we have Users. Every user can have several Accounts.
if I wanted to have a show action in the UsersController, I would probably have such a filter as:
before_filter :user_exists? only: :show
...
def user_exists
#user = User.find_by id: params[:id]
redirect_to :back, flash: { alert: "That user does not exist." } if !#user
end
Said filter would live probably in the UsersController since it makes sense, semantically speaking, to organize it there. Right?
So now let's say that I want to create an account, which of course uses the action create in the AccountsController. I want to make sure that I dont create an account for a user that no longer exists. (For instance, between the time that the user clicked "Create account" and the time he submitted the form, the user could have been deleted). So the same filter would be applied:
before_filter :user_exists? only: :create
My conundrum lies here:
Where should now live the filter?
Option #1: We move the filter to the ApplicationController.
This would work, but it has the disadvantage of de-organize my code. If I start taking this technique every time I stomp on this problem (and given Model relationships, this can happen quite often), I would end up with a code quite "spread". Not truly desirable.
Option #2: We duplicate the filter in both controllers. the disadvantage here is clear. Code harder to maintain. Duplicated code is bad.
Is there an Option #3? Is there a Rails way to solve this problem cleaner?
You could add this filter to a module and mix it in your controllers.
module UserExists
def self.included(base)
base.before_filter :user_exists? only: :create
end
def user_exists
#user = User.find_by id: params[:id]
redirect_to :back, flash: { alert: "That user does not exist." } if !#user
end
end
The self.included is called when you mix it in your classes.
class users_controller
include UserExists
I have a like model, recording which user liked which record. I used polymorphic association so a user can like many models.
Currently I use nested-resources to handle likes.
POST /items/:item_id/likes
DELETE /items/:item_id/likes/:id
Now for some reasons I want to get rid of the use of like_id by designing a better route. This is because it will be easier to cache a fragment view.
Note that item model is only one of a few models which are likable, and I want to avoid code duplication if possible.
What's a good way to design routes and controllers that will not use like_id but also allows better code reuse in controller?
Possible implementation
I was thinking of routes like this:
POST /items/:item_id/like
DELETE /items/:item_id/like
I won't use nested like resource. Instead I place a like action in items controller. It will determine if the request is a POST or a DELETE and act accordingly. This however doesn't feel DRY.
I don't know about Rails necessarily, but in Zend Framework I would create a front controller plugin to route all requests with methods 'LIKE' and 'UNLIKE' to a particular controller which then deduces which route was requested, and subsequently which resource was requested, and then performs the necessary actions to 'like' or 'unlike' that resource in the name of the requesting user.
Why? Because the user is 'like'-ing or 'unlike'-ing the resource in question, not 'creating a like' or 'deleting a like'. Sure, in the backend, the 'like' is a record in a cache or database that gets created or deleted -- but the semantics of a resource are not necessarily equivalent that of whichever method is used to persist that resource.
What you need is Singular Resources.
routes.rb
resources :items do
resource :like, only: [:create, :destroy]
end
likes_controller.rb
class LikesController < ApplicationController
before_action :load_likeable
def create
#like = Like.where(likeable: #likeable, user: current_user).first_or_create
redirect_back(fallback_location: #likeable)
end
def destroy
#like = Like.find_by(likeable: #likeable, user: current_user).destroy
redirect_back(fallback_location: #likeable)
end
private
def load_likeable
klass = [Recording].detect { |c| params["#{c.name.underscore}_id"] }
#likeable = klass.find(params["#{klass.name.underscore}_id"])
end
end
likes_helper.rb
module LikesHelper
def like_button_for(item)
if item.liked
form_tag recording_like_path(item), method: :delete do
button_tag "UnLike"
end
else
form_tag recording_like_path(item), method: :post do
button_tag "Like"
end
end
end
end
item.liked is method from Item model
I was attempting to start a test to confirm that a user can only modify an object if current_user.id and model.user_id match.
I feel like this is a validation from the model. So I might write something like:
class UserLocked < ActiveModel::Validator
def validate(record)
unless record.user_id == current_user.id
record.errors[:name] << "Sorry you cannot modify something that is not your's"
end
end
end
Which might be ok... (is there a centralized place I can put this? do I need to do anything special to reference it then?)
Writing a test for that isn't too bad either; however, I also need to prevent the controller from displaying the form to edit form. Should I be creating a separate view or just make it part of the edit page? How can I write a test for this for this in rspec...
I might be over thinking this, but I am trying to figure out what everyone else is doing. An example would be great! I've done this before in other languages/frameworks, but I am trying to "do things the right way."
Thanks!
Authorization belongs in the controller and not in the model. So you could implement a before_filter like this:
class UsersController < ApplicationController
before_filter :correct_user, only: [:edit, :update]
...
private
def correct_user
#user = User.find(params[:id])
redirect_to root_path unless current_user? #user
end
end
Of course you would need some sort a method to detect who the current user is.
You could test this with a request spec, using RSpec & Capybara. The logic is simple: you login with a user and expect that when trying to edit the info of another user you get an error message displayed. Otherwise the relevant form fields should be displayed.
For an example see http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#code:edit_update_wrong_user_tests
I have several controllers that require a correct user for their edit/update/delete actions. What is the Rails-way to accomplish the following:
Currently, in each controller I have the following code:
class FooController < ApplicationController
before_filter :correct_user, :only => [:edit, :update, :destroy]
# normal controller code
private
def correct_user
#foo = Foo.find params[:id]
redirect_to some_path unless current_user == #foo.user
end
end
I have similar code in 3 controllers. I started to bring it out to a helper like this:
module ApplicationHelper
def correct_user( object, path )
if object.respond_to? :user
redirect_to path unless object.user == current_user
end
end
But I'm wondering if this is a good way to do it. What's the accepted way to solve this?
Thank you
EDIT
The correct user check here is because I want to make sure it's only the author who can make edits/deltes to each of the objects.
To clarify, the objects would be things like Questions and Posts. I don't want to use something like CanCan as it's overkill for something simple like this.
I really like using RyanB's CanCan, which allows you to both restrict access to actions based on the user, and centralize such authorization into basically a single file.
CanCan on GitHub: https://github.com/ryanb/cancan
Screencast explaining how to setup/use it: http://railscasts.com/episodes/192-authorization-with-cancan
EDIT
No problem. I hear you on CanCan - it takes a little while to get up and running on it, but it's designed to do exactly what you're asking - per object authorization.
Alternative:
Another way to do this is move your authoriship/current_user check to the ApplicationController class, from which all of your other Controllers inherit (so they will get that code through inheritance - and you don't need to write the same code in multiple Controllers), and it would look something like...
class ApplicationController < ActionController::Base
...
helper_method :correct_user
private
def correct_user( object, path )
redirect_to path unless object.user == current_user
end
end
You should do the following :
def edit
#foo = current_user.foos.find(params[:id])
end
This way, only if the current user is the owner of the Foo he will be able to see it.