CanCan - Undefined method find for specific class - ruby-on-rails

I have a controller named Administrator. This controller has actions to anothers models. When I try to authorize this actions with CanCan I get this response.
<h1>
NoMethodError
in AdministratorController#preproduct_delete
</h1>
<pre>undefined method `find' for Administrator:Class</pre>
The controller code begins with:
class AdministratorController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
def users
authorize! :users, "Users List"
#users = User.all
end
end
The ability model has:
if user.admin? then :admin
can :users
end
The authenticate_user! method:
# See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args)
message = nil
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
message = args.pop[:message]
end
if cannot?(action, subject, *args)
message ||= unauthorized_message(action, subject)
raise AccessDenied.new(message, action, subject)
end
subject
end
I tried to remove load_and_authorize_resource from the controller, but when any action is called, CanCan redirects me to login page every time.
Thanks a lot

You did not specified the permission, what does can :user mean ? specify a permission, for all permissions use :manage.
if user.admin?
can :manage, :users
end

<h1>
NoMethodError
in AdministratorController#preproduct_delete
</h1>
Please paste preproduct_delete of AdministratorController
<pre>undefined method `find' for Administrator:Class</pre>
Is Administrator inheriting from ActiveRecord::Base ? if it doesn't have find is because is not.
it should be
class Administrator < ActiveRecord::Base
The authenticate_user! method:
# See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args)
....
end
THIS IS NOT the authenticate_user! method, this is authorize! very different, you have before_filter authenticate_user! post this method here. Something is calling preproduct_delete.

One possible reason is that the class name needs to be explicitly set. In my example I had the comments resource nested under the thread resource, and I was writing the following in controller:
class Buy::CommentsController < ApplicationController
load_and_authorize_resource :thread
This gives me undefined find method error, because comment model was under the thread namespace. When I explicitly set it like the following it works.
class Buy::CommentsController < ApplicationController
load_and_authorize_resource :thread, class: Buy::Thread

Related

How to respond with 403 (forbidden) for index action on nested resource?

Suppose we have the following setup in a ruby-on-rails (API) application:
class User < ActiveRecord::Base
has_many :posts
has_many :friends, class_name: User # Via a joins table....
end
class Post
belongs_to :user
end
When visiting /users/:id/posts, I want the logged-in user to only be able to view this data, if they are friends.
The standard implementation for this in Pundit is to use a policy scope:
class PostsController < ApplicationController
before_action :authenticate
def index
posts = policy_scope(Post.where(user_id: params[:user_id]))
render posts
end
end
class PostsPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
scope.where(user: user.friends)
end
end
end
This will prevent a user from seeing non-friends' posts. However, it produces an API response of 200 Success (with an empty response body), not 403 Forbidden - which would be preferable for the FrontEnd to receive, and display an appropriate error message.
Here's one solution that does not work:
class PostsController < ApplicationController
before_action :authenticate
def index
posts = policy_scope(Post.where(user_id: params[:user_id]))
authorize posts # <- !!!!
render posts
end
end
class PostsPolicy
def index?
record.all? { |post| user.friends_with?(post.user) }
end
end
Not only is this very inefficient, but if the user doesn't have any posts, then you'll always get a 200 Success response - which is still not ideal.
Similarly, it's not ideal to "return 403 if the response is empty" - because then you'd get error messages when viewing friends' posts, if they don't have any!
Here's a possible solution, but it feels wrong...
class PostsController < ApplicationController
before_action :authenticate
def index
user = user.find(params[:user_id])
authorize(user, :index_posts?) # <-- !!!!
posts = policy_scope(Post.where(user: user))
render posts
end
end
class UsersPolicy
def index_posts?
user.friends_with?(record)
end
end
(You could also use a more generic method name like UserPolicy#friends?, to the same affect.)
This works, but it feels like a mis-use of Pundit to be applying a UserPolicy method when authorising a Post resource!
Pundit does not allow passing additional arguments to policies. This has been a highly requested feature over the years. In particular, see this highly-relevant PR/discussion. In other words, what I'd like to be able to do is this:
class PostsController < ApplicationController
before_action :authenticate
def index
user = User.find(params[:user_id])
posts = policy_scope(Post.where(user: user))
authorize(posts, user) # <- !!!! (not valid in Pundit)
render posts
end
end
class PostsPolicy
def index?(other_user) # <- !!!! (not valid in Pundit)
user.friends_with?(other_user)
end
end
However, the feature was eventually conclusively rejected by the project maintainer in favour of using "name-spaced policies" and "form objects".
Hopefully this question is not too "opinionated", but what would you suggest? Is there a clean way to use the Pundit library whilst responding with appropriate 200 vs 403 appropriately?
Or, is there a good patch/fork/alternative I could use (preferably easy to migrate to, for a large project) that will better support my desired behaviour?

How to avoid undefined method error

I want to check if a current object's user-id is the same as the id of the current user, so I can allow some operations only to logged-in users. I am using the Devise gem to help me with authentication.
That said, I want to ask a question with a broader scope. I have build associations, at least I think so, but when I open the corresponding pages in the browser I get the error:
undefined method 'user' for nil:NilClass
I know that this error often happens when a particular object in the database is not instantiated or has no entries, but I am using the console and a PostgreSQL GUI tool, to check if the data is present.
This is a screenshot https://www.evernote.com/shard/s233/sh/305c5194-87e0-4019-9eba-9a7f5d7a2839/7c89b4842cc6efc1/res/b7879832-7829-4fe3-b81a-386b6f81cc11/skitch.png?resizeSmall&width=832
First to clarify that I understand right, here's what some things do:
If you define a method (def x) within a controller's "private" section this means, that the data is only available within your controller?
With a callback (before_action) you populate your app's REST methods with the data of the private method, it might want to use?
Now I have an image model with:
class Image < ActiveRecord::Base
mount_uploader :image, ImageUploader
belongs_to :user
belongs_to :game, inverse_of: :images
end
The user model reads like this:
class User < ActiveRecord::Base
...
has_many :images
has_many :games
validates :first_name, :last_name, presence: true
end
In the corresponding image controller I use:
class ImagesController < ApplicationController
before_action :set_image, only: [:show, :edit, :update, :destroy]
before_action :set_game
before_action :authenticate_user!
before_action :check_user
...
private
def set_image
#image = Image.find(params[:id])
end
def set_game
#game = Game.all
end
def check_user
unless (#image.user == current_user) || (current_user.admin?)
redirect_to root_url, alert: "Sorry but you are not allowed to visit this page."
end
end
def image_params
params.require(:image).permit(:title, :alt, :desc, :image, :category)
end
end
With #image.user in the check_user method I try to get the user's id. If I only use the current_user.admin? it works but not as intended, obviously.
As you can see in the screenshot above, the user_id field is populated, so I have no idea why I get this error. Maybe I forgot something?
Based on you error message, the problem is on #image.user in check_user method. Here, #image is nil. You should check if #image.nil? there.
Probably change it to:
#image = Image.find(params[:id])
unless !#image.nil? && ((#image.user == current_user) || (current_user.admin?))
BTW, you should only check user in :show, :edit, :update, :destroy like:
before_action :check_user, only: [:show, :edit, :update, :destroy]
What you're asking is something called authorization.
Authentication - does user exist?
Authorization - does user have permission?
Devise provides authentication, whilst authorization has no "standard" process for Rails.
What you're asking is the base line requirement for authorization in a Rails based application. The way to fix this is to use one of the authorization gems, namely CanCanCan or Pundit to ensure the user can change the required objects.
I'd personally set up authorization as follows:
#Gemfile
gem 'cancancan'
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Image, user_id: user.id
end
end
This will allow you to simply call can? :read, #image to validate the authorization for the user.
Fix
The real problem you have is that you're trying to call .user on a non-existent variable.
for nil:NilClass
When you see the above error, it means that you're calling a method on an undeclared variable.
Unlike other programming languages, Ruby doesn't so much treat the variable as undeclared, but as nil - confusing many developers. In short, the error means you're trying to call .user on a variable which does not have the method present; the solution being to ensure #image is declared.
-
The error seems to be caused by this:
#image.user #-> #image does not exist
Therefore, you have to check why #image has not been declared.
I would hazard a guess that the error is caused by your routes. You need to make sure you're calling the images controller properly:
#config/routes.rb
resources :images
#app/controllers/images_controller.rb
class ImagesController < ApplicationController
def show
#image = Image.find params[:id]
authorize! :read, #image
end
end
This should enable only users who own the image to view it. You'll not have to worry about authentication because that will be handled by Devise.
You can use respond_to? method to check if an object can respond to a particular method before calling it. Just like this.
object.respond_to?(:method_name)

Pundit: undefined method `authorize'

I am trying to use Pundit to authenticate access to some static views that require no database interaction:
class StaticController < ApplicationController
include Pundit
authorize :splash, :home?
def home end
end
Below is my static policy. The home? policy always returns true, so I should be able to access the home view.
class StaticPolicy < Struct.new(:user, :static)
def initialize(user, resource)
#user = user
#resource = resource
end
def home?
true
end
end
Instead I get this:
undefined method `authorize' for StaticController:Class
Pundit works perfectly if I'm authorizing a model:
def forums_index
#forums = Forum.all
authorize #forums
end
However, if I try to use the authorize method outside of an action that doesn't make use of a model I get:
undefined method `authorize' for StaticController:Class
Well, AFAIK you'll always have to authorize against either an object or a class, while CanCan already "load_and_authorize_resource", when using Pundit you already know that you have to load and authorize something yourself (sorry if I'm being too obvious here).
That said and considering that your view doesn't have DB interation, it seems to me that the best solution for your case is make some custom authorization against your user, something like
class StaticPolicy < Struct.new(:user, :static)
def initialize(user, resource)
#user = user
#resource = resource
end
def home?
authorize #user, :admin # or suppress the second parameter and let the Policy use the 'home?' method
true
end
end
and in your UserPolicy something like
class UserPolicy < ApplicationPolicy
def admin # or def home?, it's up to you
user.admin?
end
end
I didn't test it, but that's the main idea, does it make any sense? Is it clear?
Please give it a try and post any impressions, hope it helps :)

uninitialized constant of controller

Hi i am trying to use cancan but I have very irritating error, namely: uninitialized constant Edition
for controller :
class EditionsController < ApplicationController
before_filter :authenticate_user! #devise
load_and_authorize_resource
def index
end
end
with this route:
get "editions/index"
and such abilities:
user ||= User.new # guest user (not logged in)
if user.has_role? "admin"
can :manage, Edition
cannot :commission
else
can :read, :commission
end
And additional question, how i can create cancan ability for singular(name) controller ?
for example PhotoController
The problem is that you are using a custom class.
CanCan tries to singularize the controller class name to match the model class name and that sometimes isn't what we expect:
...
If the model class is namespaced differently than the controller you will need to specify the :class option.
source: https://github.com/ryanb/cancan/wiki/authorizing-controller-actions
In my case, I had a class with no model, so I just added ":class => false" at the end of the command:
load_and_authorize_resource :class => false
If you have load_and_authorize_resource defined in the ApplicationController and want to override it only for this controller:
class EditionsController < ApplicationController
skip_load_and_authorize_resource
def index
#editions = Edition.accessible_by(current_ability).order(:date)
end
end

implement a user_controller for devise with a model user?

I have some methods now under "profile" like user blocking, banning, moderation.
It feels these should belong under "user" and inside the user controller.
Is there a way to have a user_controller.rb when using devise with a user model?
Reason for this is to scope all user related methods under the user_controller instead of the profile_controller as it is now.
Yes. There is no problem with that. You can simply create users_controller.rb and interact with User model like:
class UsersController < ApplicationController
# do any stuff you need here
def block
#user = User.find(params[:id])
#user.block
end
def ban
#user = User.find(params[:id])
#user.ban
end
end
For sure, you have to create routes for this controller:
resources :users, only: [] do
member do
get :ban
get :block
end
end
Like that.

Resources