288 lines
7.4 KiB
Ruby
288 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe 'Gitlab::Graphql::Authorization' do
|
|
set(:user) { create(:user) }
|
|
|
|
let(:permission_single) { :foo }
|
|
let(:permission_collection) { [:foo, :bar] }
|
|
let(:test_object) { double(name: 'My name') }
|
|
let(:query_string) { '{ object() { name } }' }
|
|
let(:result) { execute_query(query_type)['data'] }
|
|
|
|
subject { result['object'] }
|
|
|
|
shared_examples 'authorization with a single permission' do
|
|
it 'returns the protected field when user has permission' do
|
|
permit(permission_single)
|
|
|
|
expect(subject).to eq('name' => test_object.name)
|
|
end
|
|
|
|
it 'returns nil when user is not authorized' do
|
|
expect(subject).to be_nil
|
|
end
|
|
end
|
|
|
|
shared_examples 'authorization with a collection of permissions' do
|
|
it 'returns the protected field when user has all permissions' do
|
|
permit(*permission_collection)
|
|
|
|
expect(subject).to eq('name' => test_object.name)
|
|
end
|
|
|
|
it 'returns nil when user only has one of the permissions' do
|
|
permit(permission_collection.first)
|
|
|
|
expect(subject).to be_nil
|
|
end
|
|
|
|
it 'returns nil when user only has none of the permissions' do
|
|
expect(subject).to be_nil
|
|
end
|
|
end
|
|
|
|
before do
|
|
# By default, disallow all permissions.
|
|
allow(Ability).to receive(:allowed?).and_return(false)
|
|
end
|
|
|
|
describe 'Field authorizations' do
|
|
let(:type) { type_factory }
|
|
|
|
describe 'with a single permission' do
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
|
|
end
|
|
end
|
|
|
|
include_examples 'authorization with a single permission'
|
|
end
|
|
|
|
describe 'with a collection of permissions' do
|
|
let(:query_type) do
|
|
permissions = permission_collection
|
|
query_factory do |qt|
|
|
qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
|
|
authorize permissions
|
|
end
|
|
end
|
|
end
|
|
|
|
include_examples 'authorization with a collection of permissions'
|
|
end
|
|
end
|
|
|
|
describe 'Field authorizations when field is a built in type' do
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
|
|
end
|
|
end
|
|
|
|
describe 'with a single permission' do
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.field :name, GraphQL::STRING_TYPE, null: true, authorize: permission_single
|
|
end
|
|
end
|
|
|
|
it 'returns the protected field when user has permission' do
|
|
permit(permission_single)
|
|
|
|
expect(subject).to eq('name' => test_object.name)
|
|
end
|
|
|
|
it 'returns nil when user is not authorized' do
|
|
expect(subject).to eq('name' => nil)
|
|
end
|
|
end
|
|
|
|
describe 'with a collection of permissions' do
|
|
let(:type) do
|
|
permissions = permission_collection
|
|
type_factory do |type|
|
|
type.field :name, GraphQL::STRING_TYPE, null: true do
|
|
authorize permissions
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'returns the protected field when user has all permissions' do
|
|
permit(*permission_collection)
|
|
|
|
expect(subject).to eq('name' => test_object.name)
|
|
end
|
|
|
|
it 'returns nil when user only has one of the permissions' do
|
|
permit(permission_collection.first)
|
|
|
|
expect(subject).to eq('name' => nil)
|
|
end
|
|
|
|
it 'returns nil when user only has none of the permissions' do
|
|
expect(subject).to eq('name' => nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Type authorizations' do
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
|
|
end
|
|
end
|
|
|
|
describe 'with a single permission' do
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.authorize permission_single
|
|
end
|
|
end
|
|
|
|
include_examples 'authorization with a single permission'
|
|
end
|
|
|
|
describe 'with a collection of permissions' do
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.authorize permission_collection
|
|
end
|
|
end
|
|
|
|
include_examples 'authorization with a collection of permissions'
|
|
end
|
|
end
|
|
|
|
describe 'type and field authorizations together' do
|
|
let(:permission_1) { permission_collection.first }
|
|
let(:permission_2) { permission_collection.last }
|
|
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.authorize permission_1
|
|
end
|
|
end
|
|
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
|
|
end
|
|
end
|
|
|
|
include_examples 'authorization with a collection of permissions'
|
|
end
|
|
|
|
describe 'type authorizations when applied to a relay connection' do
|
|
let(:query_string) { '{ object() { edges { node { name } } } }' }
|
|
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.authorize permission_single
|
|
end
|
|
end
|
|
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object] }
|
|
end
|
|
end
|
|
|
|
subject { result.dig('object', 'edges') }
|
|
|
|
it 'returns the protected field when user has permission' do
|
|
permit(permission_single)
|
|
|
|
expect(subject).not_to be_empty
|
|
expect(subject.first['node']).to eq('name' => test_object.name)
|
|
end
|
|
|
|
it 'returns nil when user is not authorized' do
|
|
expect(subject).to be_empty
|
|
end
|
|
end
|
|
|
|
describe 'type authorizations when applied to a basic connection' do
|
|
let(:type) do
|
|
type_factory do |type|
|
|
type.authorize permission_single
|
|
end
|
|
end
|
|
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
|
|
end
|
|
end
|
|
|
|
subject { result['object'].first }
|
|
|
|
include_examples 'authorization with a single permission'
|
|
end
|
|
|
|
describe 'when connections do not follow the correct specification' do
|
|
let(:query_string) { '{ object() { edges { node { name }} } }' }
|
|
|
|
let(:type) do
|
|
bad_node = type_factory do |type|
|
|
type.graphql_name 'BadNode'
|
|
type.field :bad_node, GraphQL::STRING_TYPE, null: true
|
|
end
|
|
|
|
type_factory do |type|
|
|
type.field :edges, [bad_node], null: true
|
|
end
|
|
end
|
|
|
|
let(:query_type) do
|
|
query_factory do |query|
|
|
query.field :object, type, null: true
|
|
end
|
|
end
|
|
|
|
it 'throws an error' do
|
|
expect { result }.to raise_error(Gitlab::Graphql::Errors::ConnectionDefinitionError)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def permit(*permissions)
|
|
permissions.each do |permission|
|
|
allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true)
|
|
end
|
|
end
|
|
|
|
def type_factory
|
|
Class.new(Types::BaseObject) do
|
|
graphql_name 'TestType'
|
|
|
|
field :name, GraphQL::STRING_TYPE, null: true
|
|
|
|
yield(self) if block_given?
|
|
end
|
|
end
|
|
|
|
def query_factory
|
|
Class.new(Types::BaseObject) do
|
|
graphql_name 'TestQuery'
|
|
|
|
yield(self) if block_given?
|
|
end
|
|
end
|
|
|
|
def execute_query(query_type)
|
|
schema = Class.new(GraphQL::Schema) do
|
|
use Gitlab::Graphql::Authorize
|
|
query(query_type)
|
|
end
|
|
|
|
schema.execute(
|
|
query_string,
|
|
context: { current_user: user },
|
|
variables: {}
|
|
)
|
|
end
|
|
end
|