palkan/action_policy-graphql

[How to] authorize specific arguments in a mutation

Tao-Galasse opened this issue ยท 1 comments

Hi there @palkan! ๐Ÿ‘‹

First of all, thanks for your amazing gem :)

Here is my issue: I would like to be able to authorize some specific arguments in a mutation, and not only the mutation as a whole.

Let's say I have a mutation to update a User. Maybe the user itself could update its first_name, but only the admin could upgrade its role, and the manager could assign it in a specific team.

I would like to do something like this:

class Mutations::UpdateUser < BaseMutation
  argument :user_id, ID, loads: Types::UserType, required: true
  argument :first_name, String, required: false # no specific authorization here
  argument :last_name, String, required: false # no specific authorization here
  argument :team_id, ID, loads: Types::Team, required: false, authorize: true
  argument :role, Types::RoleEnum, required: false, authorize: true

  def resolve(user:, **params)
    authorize! user, to: :update?
    user.update!(params)
    { user: user }
  end
end

And then, in my policy class, I could have something like this:

class UserPolicy < ApplicationPolicy
  def team? = user.manager? || user.admin?
  def role? = user.admin?

  def update? # my mutation authorization logic
end

For now, the only way to achieve this I found was to do all this argument-validation logic inside my resolver, but the implementation feels a bit lame.
For example:

def resolve(user:, team: nil, role: nil, **params)
  authorize! user, to: :update?
  authorize! user, to: :update_team? if team
  authorize! user, to: :update_role? if role
end

Is there a better way to manage this?

Thanks a lot :)

Hey! Thank you for our feedback!

We've been thinking on a workaround for this problem for a while, see: #24 (and other linked discussions). Still, no interface we agreed upon.

Using the current API, I see two options:

  • Passing input as an optional context (authorize :input, optional: true in your policy and authorize! ..., context: {input:})
  • Using scopes to filter input parameters (similarly to permitted attributes)

The latter approach is preferable if you want to support partial application, i.e., ignore unpermitted fields and continue execution with what's left.