在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):exAspArk/graphql-guard开源软件地址(OpenSource Url):https://github.com/exAspArk/graphql-guard开源编程语言(OpenSource Language):Ruby 99.1%开源软件介绍(OpenSource Introduction):graphql-guardThis gem provides a field-level authorization for graphql-ruby. Contents
UsageDefine a GraphQL schema: # Define a type
class PostType < GraphQL::Schema::Object
field :id, ID, null: false
field :title, String, null: true
end
# Define a query
class QueryType < GraphQL::Schema::Object
field :posts, [PostType], null: false do
argument :user_id, ID, required: true
end
def posts(user_id:)
Post.where(user_id: user_id)
end
end
# Define a schema
class Schema < GraphQL::Schema
use GraphQL::Execution::Interpreter
use GraphQL::Analysis::AST
query QueryType
end
# Execute query
Schema.execute(query, variables: { userId: 1 }, context: { current_user: current_user }) Inline policiesAdd class Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query QueryType use GraphQL::Guard.new end Now you can define class QueryType < GraphQL::Schema::Object field :posts, [PostType], null: false do argument :user_id, ID, required: true guard ->(obj, args, ctx) { args[:user_id] == ctx[:current_user].id } end ... end You can also define class PostType < GraphQL::Schema::Object guard ->(obj, args, ctx) { ctx[:current_user].admin? } ... end If Policy objectAlternatively, it's possible to extract and describe all policies by using PORO (Plain Old Ruby Object), which should implement a class GraphqlPolicy RULES = { QueryType => { posts: ->(obj, args, ctx) { args[:user_id] == ctx[:current_user].id } }, PostType => { '*': ->(obj, args, ctx) { ctx[:current_user].admin? } } } def self.guard(type, field) RULES.dig(type, field) end end Pass this object to class Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query QueryType use GraphQL::Guard.new(policy_object: GraphqlPolicy) end When using a policy object, you may want to allow introspection queries to skip authorization. A simple way to avoid having to whitelist every introspection type in the def self.guard(type, field) type.introspection? ? ->(_obj, _args, _ctx) { true } : RULES.dig(type, field) # or "false" to restrict an access end Priority order
class GraphqlPolicy RULES = { PostType => { '*': ->(obj, args, ctx) { ctx[:current_user].admin? }, # <=== 4 title: ->(obj, args, ctx) { ctx[:current_user].admin? } # <=== 2 } } def self.guard(type, field) RULES.dig(type, field) end end class PostType < GraphQL::Schema::Object guard ->(obj, args, ctx) { ctx[:current_user].admin? } # <=== 3 field :title, String, null: true, guard: ->(obj, args, ctx) { ctx[:current_user].admin? } # <=== 1 end class Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query QueryType use GraphQL::Guard.new(policy_object: GraphqlPolicy) end IntegrationYou can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;) CanCanCan# Define an ability class Ability include CanCan::Ability def initialize(user) user ||= User.new if user.admin? can :manage, :all else can :read, Post, author_id: user.id end end end # Use the ability in your guard class PostType < GraphQL::Schema::Object guard ->(post, args, ctx) { ctx[:current_ability].can?(:read, post) } ... end # Pass the ability Schema.execute(query, context: { current_ability: Ability.new(current_user) }) Pundit# Define a policy class PostPolicy < ApplicationPolicy def show? user.admin? || record.author_id == user.id end end # Use the ability in your guard class PostType < GraphQL::Schema::Object guard ->(post, args, ctx) { PostPolicy.new(ctx[:current_user], post).show? } ... end # Pass current_user Schema.execute(query, context: { current_user: current_user }) Error handlingBy default class SchemaWithErrors < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query QueryType use GraphQL::Guard.new( # By default it raises an error # not_authorized: ->(type, field) do # raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") # end # Returns an error in the response not_authorized: ->(type, field) do GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") end ) end In this case executing a query will continue, but return SchemaWithErrors.execute("query { posts(user_id: 1) { id title } }") # => { # "data" => nil, # "errors" => [{ # "messages" => "Not authorized to access Query.posts", # "locations": { "line" => 1, "column" => 9 }, # "path" => ["posts"] # }] # } In more advanced cases, you may want not to return class GraphqlPolicy RULES = { PostType => { '*': { guard: ->(obj, args, ctx) { ... }, not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") } } title: { guard: ->(obj, args, ctx) { ... }, not_authorized: ->(type, field) { nil } # simply return nil if not authorized, no errors } } } def self.guard(type, field) RULES.dig(type, field, :guard) end def self.not_authorized_handler(type, field) RULES.dig(type, field, :not_authorized) || RULES.dig(type, :'*', :not_authorized) end end class Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query QueryType mutation MutationType use GraphQL::Guard.new( policy_object: GraphqlPolicy, not_authorized: ->(type, field) { handler = GraphqlPolicy.not_authorized_handler(type, field) handler.call(type, field) } ) end Schema maskingIt's possible to hide fields from being introspectable and accessible based on the context. For example: class PostType < GraphQL::Schema::Object field :id, ID, null: false field :title, String, null: true do # The field "title" is accessible only for beta testers mask ->(ctx) { ctx[:current_user].beta_tester? } end end InstallationAdd this line to your application's Gemfile: gem 'graphql-guard' And then execute:
Or install it yourself as:
TestingIt's possible to test fields with # Your type class QueryType < GraphQL::Schema::Object field :posts, [PostType], null: false, guard ->(obj, args, ctx) { ... } end # Your test require "graphql/guard/testing" posts = QueryType.field_with_guard('posts') result = posts.guard(obj, args, ctx) expect(result).to eq(true) If you would like to test your fields with policy objects: # Your type class QueryType < GraphQL::Schema::Object field :posts, [PostType], null: false end # Your policy object class GraphqlPolicy def self.guard(type, field) ->(obj, args, ctx) { ... } end end # Your test require "graphql/guard/testing" posts = QueryType.field_with_guard('posts', GraphqlPolicy) result = posts.guard(obj, args, ctx) expect(result).to eq(true) DevelopmentAfter checking out the repo, run To install this gem onto your local machine, run ContributingBug reports and pull requests are welcome on GitHub at https://github.com/exAspArk/graphql-guard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct. LicenseThe gem is available as open source under the terms of the MIT License. Code of ConductEveryone interacting in the Graphql::Guard project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct. |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论