From 02225e31960f40f24f9e6fde39ab2ba856d3e378 Mon Sep 17 00:00:00 2001 From: Kyle Sferrazza Date: Sun, 7 May 2023 13:27:43 -0400 Subject: [PATCH] upgrade graphql to latest 1.0, remove graphql-guard --- Gemfile | 3 +-- Gemfile.lock | 7 ++---- app/graphql/hourglass_schema.rb | 20 ++++++++-------- app/graphql/types/base_field.rb | 13 +++++++++++ app/graphql/types/base_object.rb | 23 +++++++++++++++++++ app/graphql/types/grading_comment_type.rb | 1 - app/graphql/types/grading_lock_type.rb | 28 +++++++++++++++-------- app/models/grading_lock.rb | 1 + 8 files changed, 68 insertions(+), 28 deletions(-) diff --git a/Gemfile b/Gemfile index 376f92373..df9c2a3ad 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,8 @@ gem 'rails', '~> 6.1' # Use postgresql as the database for Active Record gem 'pg', '>= 0.18', '< 2.0' -gem 'graphql', '~> 1.12.17' +gem 'graphql', '~> 1.0' gem 'graphql-batch' -gem 'graphql-guard' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem 'webpacker', '~> 6.0.0.rc.5' diff --git a/Gemfile.lock b/Gemfile.lock index 9df150b68..26f0109fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,12 +137,10 @@ GEM ffi (1.15.5) globalid (1.1.0) activesupport (>= 5.0) - graphql (1.12.24) + graphql (1.13.19) graphql-batch (0.5.2) graphql (>= 1.10, < 3) promise.rb (~> 0.7.2) - graphql-guard (2.0.0) - graphql (>= 1.10.0, < 2) hana (1.3.7) hashie (5.0.0) headless (2.3.1) @@ -366,9 +364,8 @@ DEPENDENCIES database_cleaner devise (~> 4.9.0) factory_bot_rails - graphql (~> 1.12.17) + graphql (~> 1.0) graphql-batch - graphql-guard headless json-schema (~> 4.0) listen (~> 3.3) diff --git a/app/graphql/hourglass_schema.rb b/app/graphql/hourglass_schema.rb index 0f26d4c08..7f611fc6f 100644 --- a/app/graphql/hourglass_schema.rb +++ b/app/graphql/hourglass_schema.rb @@ -15,11 +15,11 @@ class HourglassSchema < GraphQL::Schema # Feedback and error messages in development mode will be # more informative than in production/test modes. if Rails.env.development? - use GraphQL::Guard.new( - not_authorized: lambda do |type, field| - GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") - end, - ) + # use GraphQL::Guard.new( + # not_authorized: lambda do |type, field| + # GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") + # end, + # ) def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: @@ -38,11 +38,11 @@ def self.execute(query_str = nil, **kwargs) super(query_str, **kwargs) end else - use GraphQL::Guard.new( - not_authorized: lambda do |type, field| - GraphQL::ExecutionError.new("You do not have permission to view that data.") - end, - ) + # use GraphQL::Guard.new( + # not_authorized: lambda do |type, field| + # GraphQL::ExecutionError.new("You do not have permission to view that data.") + # end, + # ) def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 611eb0560..ba3667f01 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -3,5 +3,18 @@ module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument + def guard(proc) + @guard_proc = proc + end + + def authorized?(obj, args, ctx) + return true unless @guard_proc + + wrapped = Types::GuardWrapper.new(self, obj) + answer = @guard_proc.call(wrapped, args, ctx) + return true if answer + + raise GraphQL::ExecutionError, "Not authorized to access #{self.owner_type.graphql_name}.#{self.graphql_name}." + end end end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb index a69458deb..426f962d9 100644 --- a/app/graphql/types/base_object.rb +++ b/app/graphql/types/base_object.rb @@ -1,10 +1,33 @@ # frozen_string_literal: true module Types + class GuardWrapper + attr_accessor :class, :object + + def initialize(cls, object) + @class = cls + @object = object + end + end + # The base class of Hourglass objects returned by GraphQL class BaseObject < GraphQL::Schema::Object field_class Types::BaseField + def self.guard(proc) + @guard_proc = proc + end + + def self.authorized?(obj, ctx) + return true unless @guard_proc + + wrapped = Types::GuardWrapper.new(self, obj) + answer = @guard_proc.call(wrapped, nil, ctx) + return true if answer + + raise GraphQL::ExecutionError, "You do not have permission to view that information." + end + module Guards def self.exam_role(user, ctx) ctx[:access_cache]&.dig(:role_for_exam, user.id) || Exam.roles[:no_reg] diff --git a/app/graphql/types/grading_comment_type.rb b/app/graphql/types/grading_comment_type.rb index fae614cae..5d90c23f5 100644 --- a/app/graphql/types/grading_comment_type.rb +++ b/app/graphql/types/grading_comment_type.rb @@ -26,6 +26,5 @@ def bnum field :message, String, null: false field :points, Float, null: false field :creator, Types::UserType, null: false - field :preset_comment, Types::PresetCommentType, null: true end end diff --git a/app/graphql/types/grading_lock_type.rb b/app/graphql/types/grading_lock_type.rb index 7435f66e1..c53c3904c 100644 --- a/app/graphql/types/grading_lock_type.rb +++ b/app/graphql/types/grading_lock_type.rb @@ -3,24 +3,31 @@ module Types class GradingLockType < Types::BaseObject implements GraphQL::Types::Relay::Node - global_id_field :id - field :id, ID, null: false, guard: ->(_obj, _args, _ctx) { true } + global_id_field :id - guard Guards::VISIBILITY + guard Guards::ALL_STAFF - field :registration, Types::RegistrationType, null: false + field :registration, Types::RegistrationType, null: false do + guard Guards::VISIBILITY + end def registration RecordLoader.for(Registration).load(object.registration_id) end - field :grader, Types::UserType, null: true, guard: ->(obj, _args, ctx) { - obj.object.grader_id.nil? || obj.object.visible_to?(ctx[:current_user], Guards.exam_role(ctx[:current_user], ctx), Guards.course_role(ctx[:current_user], ctx)) - } + + field :grader, Types::UserType, null: true do + guard ->(obj, _args, ctx) { + obj.object.grader_id.nil? || obj.object.visible_to?(ctx[:current_user], Guards.exam_role(ctx[:current_user], ctx), Guards.course_role(ctx[:current_user], ctx)) + } + end def grader RecordLoader.for(User).load(object.grader_id) end - field :completed_by, Types::UserType, null: true, guard: ->(obj, _args, ctx) { - obj.object.completed_by_id.nil? || obj.object.visible_to?(ctx[:current_user], Guards.exam_role(ctx[:current_user], ctx), Guards.course_role(ctx[:current_user], ctx)) - } + + field :completed_by, Types::UserType, null: true do + guard ->(obj, _args, ctx) { + obj.object.completed_by_id.nil? || obj.object.visible_to?(ctx[:current_user], Guards.exam_role(ctx[:current_user], ctx), Guards.course_role(ctx[:current_user], ctx)) + } + end def completed_by RecordLoader.for(User).load(object.completed_by_id) end @@ -29,6 +36,7 @@ def completed_by def qnum RecordLoader.for(Question).load(object.question_id).then{|q| q.index} end + field :pnum, Integer, null: false def pnum RecordLoader.for(Part).load(object.part_id).then{|q| q.index} diff --git a/app/models/grading_lock.rb b/app/models/grading_lock.rb index 6dd9261eb..494fb2faf 100644 --- a/app/models/grading_lock.rb +++ b/app/models/grading_lock.rb @@ -25,6 +25,7 @@ def valid_qp delegate :exam_version, to: :registration delegate :exam, to: :registration + delegate :course, to: :exam scope :incomplete, -> { where(completed_by: nil) } scope :complete, -> { where.not(completed_by: nil) }