Reputation: 2735
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!
Upvotes: 14
Views: 7603
Reputation: 1660
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
Upvotes: 0
Reputation: 2978
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.
Upvotes: 1
Reputation: 3247
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.
Upvotes: 1
Reputation: 2735
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
Upvotes: 20