Graphql-ruby habtm 中的范围界定,嵌套查询中的数据泄漏

Graphql-ruby scoping in habtm, data leak in nested query

这是使用标准 graphql-ruby 设置时发生的数据泄漏示例。

使用下面的 graphql 嵌套请求,响应 returns 数据嵌套在公司 1 下,属于公司 2。我希望响应仅限于属于公司的会计师日志嵌套在.

就是这样,这是在泄露信息。

问题是我们如何修补漏洞,以便响应中返回的唯一数据及其嵌套对象是属于公司的数据(根对象)。

这个查询:

query { 
  company(id: "1") {
    id
    name
    activityLog {
      id
      activityAt
      accountant {
        id
        name
      }
      companyId
    }
    accountants {
      id
      name
      activityLog {
        id
        activityAt
        companyId
      }
    }
  }
}

returns 此回复:

{
  "data": {
    "company": {
      "id": "1",
      "name": "AwsomeCo",
      "activityLog": [
        {
          "id": "1",
          "activityAt": "2019-10-12 16:40:13 UTC",
          "accountant": {
            "id": "100",
            "name": "Mr Smith",
          },
          "companyId": "1"
        }
      ],
      "accountants": [
        {
          "id": "100",
          "name": "Mr Smith"
          "activityLog": [
            {
              "id": "1",
              "activityAt": "2019-10-12 16:40:13 UTC",
              "companyId": "1"
            },
            {
              "id": "2",
              "activityAt": "2019-10-11 16:40:13 UTC",
              "companyId": "2"  // OTHER COMPANY, NEED TO PRESERVE PARENT SCOPE
            },
          ],
        }
      }
    }
  }
}

在公司 1 的嵌套元素中泄漏公司 2 的事务日志数据。

同样,问题是:我们如何保留范围,仅在所显示的公司上下文中显示数据?

重现代码:

GraphQL 类型(使用 graphql-ruby gem)

#query_type.rb
module Types
  class QueryType < Types::BaseObject
    # Add root-level fields here.
    # They will be entry points for queries on your schema.
    field :company_leak, Types::ExampleLeak::CompanyType, null: false do
      argument :id, ID, required: true
    end
    field :companies_leak, [ Types::ExampleLeak::CompanyType ], null: false

    def company_leak(id: )
      Company.find(id)
    end

    def companies_leak
      Company.all
    end
  end
end

module Types
  module ExampleLeak
    class CompanyType < BaseObject
      field :id, ID, null: false
      field :name, String, null: false
      field :transaction_logs, [Types::ExampleLeak::TransactionLogType], null: true
      field :accountants, [Types::ExampleLeak::AccountantType], null: true
    end
  end
end

module Types
  module ExampleLeak
    class AccountantType < BaseObject
      field :id, ID, null: false
      field :name, String, null: false
      field :transaction_logs, [Types::ExampleLeak::TransactionLogType], null: true
      field :companies, [Types::ExampleLeak::CompanyType], null: true
    end
  end
end

module Types
  module ExampleLeak
    class TransactionLogType < BaseObject
      field :id, ID, null: false
      field :activity_at, String, null: false
      field :company_id, ID, null: false
      field :accountant, Types::ExampleLeak::AccountantType, null: false
    end
  end
end

型号

class Company < ApplicationRecord
  has_and_belongs_to_many :accountants
  has_many :transaction_logs
end

class Accountant < ApplicationRecord
  has_and_belongs_to_many :companies
  has_many :transaction_logs
end

class TransactionLog < ApplicationRecord
  belongs_to :company
  belongs_to :accountant
end

seeds.rb

awesomeco = Company.create!(name: 'AwesomeCo')
boringco = Company.create!(name: 'BoringCo') 
mr_smith = Accountant.create!(name: "Mr. Smith")
awesomeco.accountants << mr_smith
boringco.accountants << mr_smith
mr_smith.transaction_logs.create!(company: awesomeco, activity_at: 1.day.ago)
mr_smith.transaction_logs.create!(company: boringco, activity_at: 2.days.ago)

Public 包含完整代码的 repo,用作教育资源:

https://github.com/rubynor/graphql-ruby-training-ground

我们可以按如下方式更新 class AccountantType < BaseObject 中的字段来解析事务日志:

  field :transaction_logs, [Types::ExampleLeak::TransactionLogType], null: true,
    resolve: ->(obj, args, ctx) {
      company_leak = ctx.irep_node.parent.parent.arguments[:id]
      companies_leak = ctx.parent.parent.object.object.id
      TransactionLog.where(id: company_leak.present? ? company_leak : companies_leak)
    }

如果给公司 ID 一个参数,它将根据该 ID 获取事务日志,否则根据其父 Accountant

听起来像是 Pundit 和范围界定的完美用例。这样一来,如果用户验证了他们的查询,就会自动确定正确的公司范围