Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 943
user664833 Avatar asked Jan 16 '14 20:01

user664833


1 Answers

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.

like image 115
Rob Avatar answered Sep 19 '22 06:09

Rob