exAspArk / graphql-guard

Simple authorization gem for GraphQL :lock:
MIT License
471 stars 36 forks source link

Authorization on Enum Values #11

Closed micahlisonbee closed 6 years ago

micahlisonbee commented 6 years ago

I'd like to be able to restrict certain enum input values to only super_admin users. Is there a way to do something like this? (This doesn't work obviously)

Types::OrganizationTypeEnumType = GraphQL::EnumType.define do
  name 'OrganizationTypeEnumType'
  description 'Enum for the possible organization types'

  value('PROVIDER', 'provider', value: 'provider'), guard: ->(obj, _args, ctx) { ctx[:current_user].super_admin? }
  value('CUSTOMER', 'customer', value: 'customer')
  value('THIRD_PARTY', 'third_party', value: 'third_party')
end
exAspArk commented 6 years ago

@micahlisonbee hey, thanks for opening the issue!

Unfortunately, it's not possible to pass custom tags (like guard) for the value method. You may open an issue in https://github.com/rmosolgo/graphql-ruby.

If you want, you can try to use GraphQL::Schema::Warder feature with custom filters (haven't tested). For example:

# controller
query = ...
admin_filter = AdminFilter.new(current_user)
result = YourSchema.execute(query, except: admin_filter)

# admin_filter
class AdminFilter
  def initialize(user)
    @user = user
  end

  def call(member, ctx)
    member.name == 'PROVIDER' && member.description == 'provider' && member.value == 'provider' && !ctx[:current_user].super_admin? 
    # ideally should be something like:
    # member.metadata[:guard]&.call(ctx)
    # but metadata is empty even with extended GraphQL::EnumType::EnumValue.accepts_definitions
  end
end

In general, schema masking (introspection) is a little bit different from query authorization. Currently, it's not supported by graphql-guard gem but maybe it can be implemented in the future. Not sure how the API will look like and how to differentiate between guard for authorization and guard for schema masking :)

E.g.

field :posts, !types[!PostType] do
  argument :user_id, !types.ID

  guard ->(obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
  guard_schema ->(ctx) { ctx[:current_user].admin? } # has only context
end

Closing the issue for now. Feel free to add your thoughts :)

micahlisonbee commented 6 years ago

@exAspArk thanks for the thoughtful response. I think filtering is probably the to go for now as you've outlined in your AdminFilter example. I'll give it a go and brainstorm on other options.

Thanks again.