Pundit policies with two input parameters
Asked Answered
R

4

14

I'm pretty new with Rails and I have a problem with the following policies (using Pundit): I'd like to compare two objects: @record and @foo, as you can see here:

class BarPolicy < ApplicationPolicy
  def show?
    @record.foo_id == @foo
  end
end

I don't reach to find a good way to pass a second parameter to pundit methods (@foo).

I'd like to do something like:

class BarsController < ApplicationController
  def test
    authorize bar, @foo, :show? # Throws ArgumentError
    ...
  end
end

But the Pundit authorize method allows only two parameters. Is there a way to solve this issue?

Thanks!

Riplex answered 29/1, 2015 at 14:5 Comment(0)
R
20

I found the answer at here.

Here is my way:

Add a pundit_user function in ApplicationController:

class ApplicationController < ActionController::Base
include Pundit
def pundit_user
    CurrentContext.new(current_user, foo)
end

Create the CurrentContext class:

/lib/pundit/current_context.rb
class CurrentContext
  attr_reader :user, :foo

  def initialize(user, foo)
    @user = user
    @foo = foo
  end
end

Update the initialize Pundit method.

class ApplicationPolicy
  attr_reader :user, :record, :foo

  def initialize(context, record)
    @user = context.user
    @foo = context.foo
    @record = record
  end
end
Riplex answered 30/1, 2015 at 15:56 Comment(3)
Yes this is correct way to document it.Sola
What's the point to write so much code just to pass a param to a single policy method when no other methods need this.Gustav
I agree, unfortunately some purists determined it's a "code smell" but it's just confusing to mere mortals like me.Fao
E
1

Relying on more than the current user and a domain model is a code smell, but in case it really is required, then you can use a custom query method with any number of parameters and raise an exception if the requirements don't met:

class BarPolicy < ApplicationPolicy
  def authorize_test?(foo)
    raise Pundit::NotAuthorizedError, "not authorized to test" unless record.foo_id == foo
  end
end

class BarsController < ApplicationController
  def test
    skip_authorization && BarPolicy.new(current_user, @record).authorize_test?(@foo)
    ...
  end
end

The skip_authorization && part is not required if after_action :verify_authorized is not used, I just wanted to show a one-liner that can be used in this case to get rid of the not-authorized exception while still having the requirement to authorize the action.

Enterprising answered 13/5, 2020 at 3:59 Comment(0)
G
1

I don't understand what this idea is that it's bad coding to pass extra parameters to Pundit. I mean, sure, it's like a big case statement. If you can avoid it by better design then do so but you'll commonly be faced with the choice to either splatter authorization logic into other files or pass extra information into Pundit.

Here's the code I wrote to let me use my Pundit class to check if a user was allowed to sort on a given column (resource_class is another method I wrote that returns the associated activerecord class in the associated controller):

    def authorize_sort(column, record = resource_class)
        Pundit.policy!(user, record)
        unless policy.allow_sort?(column)
            raise NotAuthorizedError, "Not authorized to sort on column #{column} on record #{record}"
        end
    end

This is really inevitable if you don't want your authorization logic spread out over more classes and want to allow users to sort on fields other users can't see. Absent creating a special Active record Sort object this is unavoidable as it's not a property of any single object you are returning but the column on which they are sorted.

Groos answered 20/10, 2021 at 15:38 Comment(0)
L
0

in addition from @Rowandish, in my case i had to use query_parameters (to access the property id) at the context:

class CurrentContext
  attr_reader :user, :record, :article_id

  def initialize(user, record)
    @user = user
    @article_id = record.query_parameters['id']
  end

end

and defined the pundit user like this:

def pundit_user
    CurrentContext.new(current_user, request)
end

and in my controller i am doing this request

 def index
    @article = Article.find(params[:id])
    render json: policy_scope(article)
 end

finally my police is looking like this:

class ArticlePolicy < ApplicationPolicy
  attr_reader :user, :article

  def initialize(context, record)
    @user = context.user
    @article_id = context.article_id
    @articles = record
  end

  class Scope < ArticlePolicy
    
    def resolve
        Article.find(@article_id) if ArticleUser.where(article_id: @article_id, user_id: @user.id).present?
    end

  end
end
Latt answered 31/10, 2022 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.