Why are `scope`-oriented actions (particularly `index` actions) treated differently in Pundit?
Asked Answered
C

1

10

I am writing with respect to https://github.com/elabs/pundit#scopes

I am under the impression that authorization should answer the question Are you allowed access to this resource?, i.e. a true/false answer. This is the case with all actions except index, which, according to Pundit's docs, should return different ActiveRecord::Relation's depending on who is asking. For example, an admin gets scope.all, while a regular user gets scope.where(:published => true).

app/policies/post_policy.rb

class Scope < Struct.new(:user, :scope)
  def resolve
    if user.admin?
      scope.all
    else
      scope.where(:published => true)
    end
  end
end

app/controllers/posts_controller.rb

def index
  @posts = policy_scope(Post)
end

My reservation is that this is a slippery slope, and soon I will be adding presentation to the scopes (e.g. scope.all.order('created_at ASC')) -- and it just feels weird doing so in an authorization policy.

Of course I could move that to the controller...

def index
    @post = policy_scope(post)
    if user.admin?
        @post = @post.order( 'created_at ASC' )
    end
end

...but is that the controller's job? And I don't think it would be right to add such a call to the view. So maybe it should be a model method?

What would you say are the pros/cons of doing the following instead?

app/controllers/posts_controller.rb

This keeps index just like the other methods, with one call to authorize, and one call to a model method.

def index
  authorize(Post)
  @posts = Post.index(current_user)
end

app/policies/post_policy.rb

This simply gives a true/false answer. Are you authorized? Yes or no.

def index?
    user.admin? || user.regular_user?
end

app/models/post.rb

And in the model we can get as fancy as we like.

def self.index(user)
  if user.admin?
    Post.all.order('created_at ASC')
  else
    Post.where(user_id: user.id)
  end
end

Thoughts?

Crappie answered 16/1, 2014 at 20:52 Comment(0)
I
12

My understanding of authorization vs scopes in Pundit is as follows:

authorization: 'is this user allowed to act upon (create/update/destroy) this resource?'

within scope : 'should this user be able to see (index/show) this resource?'

Authorization (authorize @resource) defers to permitted_attributes in ResourcePolicy for the answer.

Scopes (policy_scope(Resource)) defer to resolve.

I believe the reasoning behind Pundit's scopes is that there should be only one location in your code where you define who should have access to what resources.

You could, as you've described, implement the same behavior in your controllers or your views. However, putting the code into a Policy guards against unauthorized access should you happen to forget to scope appropriately in one of your controller methods.

I think of policy_scope() as the way to restrict visibility, while other result refinements (e.g. sorting) can take place at the controller level. There's no doubt a lot of personal preference at play, however.

Ideo answered 10/3, 2014 at 0:43 Comment(2)
It seems more to me like authorize is for checking if user has permission to access an individual resource, whereas scoping is for filtering what gets returned in a index action, i.e. scoping doesn't raise a 403 forbidden error like authorize does, it's just used to strip away records that are not owned by current_user.Alarick
Even Pundit's official github page has this: def show @user = authorize User.find(params[:id]) end github.com/elabs/punditAlarick

© 2022 - 2024 — McMap. All rights reserved.