From fafa6e4ed279e5faec48f38f1fb27276ff95d4d3 Mon Sep 17 00:00:00 2001 From: exAspArk Date: Tue, 12 Sep 2017 10:39:55 -0400 Subject: [PATCH] Add info how to use not_authorized handlers per each field --- README.md | 104 ++++++++++++++++++-------- spec/fixtures/inline_schema.rb | 2 +- spec/fixtures/policy_object_schema.rb | 2 +- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4c34efd..663979b 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ This gem provides a field-level authorization for [graphql-ruby](https://github. * [Inline policies](#inline-policies) * [Policy object](#policy-object) * [Priority order](#priority-order) -* [Error handling](#error-handling) * [Integration](#integration) * [CanCanCan](#cancancan) * [Pundit](#pundit) +* [Error handling](#error-handling) * [Installation](#installation) * [Testing](#testing) * [Development](#development) @@ -39,7 +39,7 @@ PostType = GraphQL::ObjectType.define do name "Post" field :id, !types.ID - field :title, !types.String + field :title, types.String end # Define a query @@ -163,34 +163,6 @@ Schema = GraphQL::Schema.define do end -## Error handling - -By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to field is not authorized. -You can change this behavior, by passing custom `not_authorized` lambda. For example: - -
-SchemaWithErrors = GraphQL::Schema.define do
-  query QueryType
-  use GraphQL::Guard.new(
-    # Returns an error in the response
-    not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
-
-    # By default it raises an error
-    # not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
-  )
-end
-
- -In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`: - -
-SchemaWithErrors.execute("query { posts(user_id: 1) { id title } }")
-# => {
-#   "data" => nil,
-#   "errors" => [{ "messages" => "Not authorized to access Query.posts", "locations": { ... }, "path" => ["posts"] }]
-# }
-
- ## Integration You can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;) @@ -244,6 +216,78 @@ end Schema.execute(query, context: { current_user: current_user }) +## Error handling + +By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to the field is not authorized. +You can change this behavior, by passing custom `not_authorized` lambda. For example: + +
+SchemaWithErrors = GraphQL::Schema.define do
+  query QueryType
+  use GraphQL::Guard.new(
+    # By default it raises an error
+    # not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
+
+    # Returns an error in the response
+    not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
+  )
+end
+
+ +In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`: + +
+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 `errors` only for some unauthorized fields. Simply return `nil` if user is not authorized to access the field. You can achieve it, for example, by placing the logic into your `PolicyObject`: + +
+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
+
+Schema = GraphQL::Schema.define do
+  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
+
+ ## Installation Add this line to your application's Gemfile: diff --git a/spec/fixtures/inline_schema.rb b/spec/fixtures/inline_schema.rb index fc557bf..64926e5 100644 --- a/spec/fixtures/inline_schema.rb +++ b/spec/fixtures/inline_schema.rb @@ -5,7 +5,7 @@ module Inline name "Post" guard ->(_post, _args, ctx) { ctx[:current_user].admin? } field :id, !types.ID - field :title, !types.String + field :title, types.String end QueryType = GraphQL::ObjectType.define do diff --git a/spec/fixtures/policy_object_schema.rb b/spec/fixtures/policy_object_schema.rb index b705e95..246f4c8 100644 --- a/spec/fixtures/policy_object_schema.rb +++ b/spec/fixtures/policy_object_schema.rb @@ -4,7 +4,7 @@ module PolicyObject PostType = GraphQL::ObjectType.define do name "Post" field :id, !types.ID - field :title, !types.String + field :title, types.String end QueryType = GraphQL::ObjectType.define do