Pundit: Access a controller's params hash (or workaround) in a policy

Created on 24 Apr 2014  路  16Comments  路  Source: varvet/pundit

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

Most helpful comment

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.

All 16 comments

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:

  • What exactly are you trying to do?
  • What option would you pass in, in which situation would you want to override the settings in the policy?

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

epinault picture epinault  路  6Comments

mackshkatz picture mackshkatz  路  4Comments

richjdsmith picture richjdsmith  路  4Comments

jdwolk picture jdwolk  路  3Comments

dowi picture dowi  路  4Comments