There may be a really obvious answer, but I'm not seeing it.
In my policy, I need to access the params hash sent with any Rails request.
class UploadsController < ApplicationController
def show
@upload = Upload.find(params[:id])
authorize @upload
end
end
class UploadPolicy
attr_reader :user, :upload
def initialize(user, upload)
@user = user
@upload = upload
end
def show?
@user.has_access || params[:some_override] # Here is where I need to access my params.
end
end
You're not missing an obvious answer, it's currently not intended to pass in any options (and no request params either) into the policy (See a few previous issues for a few reasons). You could help us in designing an adequate solution with answers to the following questions:
Thanks for the fast response thomasklemm! Also pundit has been awesome for us so far, so still an awesome gem :+1:
What exactly are you trying to do?
When a user hits the uploads#show action, I want to know if params[:embedded] == "1". If this is the case, I want to do a different type of check in my policy.
Seems like this might be outside the scope of Pundit. I propose another potential solution for you:
class Upload < AR::Base
# ...
attr_accessor :embedded
# ...
end
class UploadsController < ApplicationController
def show
@upload = Upload.find(params[:id])
@upload.embedded = params[:embedded]
authorize @upload
end
end
class UploadPolicy
attr_reader :user, :upload
def initialize(user, upload)
@user = user
@upload = upload
end
def show?
@user.has_access || @upload.embedded
end
end
Hope this helps.
That's awesome. I had no idea thats how attr_accessor works. Thanks a lot!
@johnotander Good thought, thanks for sharing!
Actually, this is the first time I feel it would drive good code if we would allow to pass in options to the authorize call. This is not equivalent to passing in the params hash (which IMO should never be passed to the policy).
# controller
class AnyController < ApplicationController
def any_action
load_upload
# There are two params that are required for validating
# if a user can view an upload
context = params[:embedded] ? :embedded : :on_site
show_key = params[:show_key]
# We pass those options in
# NOTE: We should never pass in the params hash unsanitized (IMO)!
authorize @upload, :show?, { context: context, show_key: show_key }
# ...
end
end
# policy
class UploadPolicy < ApplicationPolicy
alias_method :upload, :record
def show?(options = {})
context = options[:context] || :default
# protected uploads can only be viewed
# by a user who is authorized by some kind
# of relationship
if upload.protected?
return false unless user.may_view?(upload)
end
# any registered user may view uploads
# on our site (as long as they aren't protected)
#
# uploads can be marked as 'only show on site / do not show embedded'
if context == (:on_site || :default)
user
elsif context == :embedded
user && upload.may_be_embedded?
end
end
end
Wait thomasklemm, is it possible to pass a hash into a policy like in your example?
And yes, passing params potentially controlled by a user into a policy would be bad. But passing in a hash would save me some awkward if else blocks in the controller that end up splitting "policy" logic
@donpinkusno, it's not possible yet, but there have been a few PRs requesting that options can be passed into authorizr.
@thomasklemm When I follow your example, I get an error that tells me the object passed to authorize is not a symbol???
I've created a few wrappers because of the inability to pass simple values on to the policy:
class PermissionCheck
attr_reader(:group, :permission, :target)
def initialize(permission, options = {})
@permission = permission
@group = options[:group]
@target = options[:target]
end
end
And for the permitted_attributes, I am passing in a context variable, since I might have to get permitted_attributes for searching, or for updating/creating.
The reason I'm wanting to access the params hash in the policy is to make sure that the current_user is the one from the params, i.e., that the user with id 2 isn't able to access /users/1/reviews/new.
I'm having the same issue as @thenickcox: I'd like to confirm params[:user_id] == current_user.id.
I love this gem. LOVE it. I have only used it in two application so far though, so I'm still learning.
I am using Devise for user sessions and I also have user roles implemented with enums in the model and integers in the db. I want users that are logged in with sufficient authority defined by their roles to be able to create other users and limit the role they are allowed to bestow on the user they are creating.
What I'd like to be able to do is implement my UserPolicy such that the create? method only returns true if that user being created does not have too elevated of a role. I indeed plan on limiting the choice logged in users have in the view, but that doesn't stop the request from being possible. For example, I want super admins to be able to create admins, but I don't want admins to be able to create super admins.
What could I do in this case with how Pundit work now?
@vergenzt @thenickcox I'm also having the same issue, any magic worked for you?
Hello,
I'm using
def banner
authorize params, :banner, policy_class: Api::SharedHostingPolicy
end
and
class Api::SharedHostingPolicy < ApplicationPolicy
ALLOWED_KINDS = [:light, :dark]
def banner
ALLOWED_KINDS.include? record[:kind].to_sym
end
end
Do you know a better way? Thanks! 馃槄
I don't know if this is better but I did it this way.
In my app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :check_user
def index
@posts = Post.all
end
def new
@post = Post.new
end
#OTHER METHODS
private
def check_user
# params[:key] will pass the 'key' parameter
# get_method(params[:action]) will return 'index?' or 'new?'
# get_class(self) will return 'PostPolicy' class, you can add parameter.split('::').last if you have namespaces
authorize params[:key], get_method(params[:action]), :policy_class => get_class(self)
end
def get_class parameter
parameter.class.to_s.gsub('sController', 'Policy').constantize
end
def get_method parameter
parameter.to_s + '?'
end
end
And in my app/policies/post_policy.rb, I have:
class PostPolicy < ApplicationPolicy
def index?
# record variable contains the key that I passed
record.present?
end
def new?
# record variable contains the key that I passed
record.present?
end
#OTHER METHODS
end
I did it this way to avoid calling authorize to every method and be able to pass a variable from the controller to the policy for validation.
Most helpful comment
The reason I'm wanting to access the params hash in the policy is to make sure that the
current_useris the one from theparams, i.e., that the user with id 2 isn't able to access/users/1/reviews/new.