diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-18 09:11:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-18 09:11:52 +0300 |
commit | 4d16568658ac6fb0003b407e07a76c11e607f44f (patch) | |
tree | 9084e7660f101d2cd70568f293257678ac5f2ef5 /spec/lib/gitlab/graphql/authorize | |
parent | f5410eefec8642bed6e7e3051319c52d7cbfb101 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/lib/gitlab/graphql/authorize')
-rw-r--r-- | spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb | 253 | ||||
-rw-r--r-- | spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb | 77 |
2 files changed, 17 insertions, 313 deletions
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb deleted file mode 100644 index c88506899cd..00000000000 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ /dev/null @@ -1,253 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# Also see spec/graphql/features/authorization_spec.rb for -# integration tests of AuthorizeFieldService -RSpec.describe Gitlab::Graphql::Authorize::AuthorizeFieldService do - def type(type_authorizations = []) - Class.new(Types::BaseObject) do - graphql_name 'TestType' - - authorize type_authorizations - end - end - - def type_with_field(field_type, field_authorizations = [], resolved_value = 'Resolved value', **options) - Class.new(Types::BaseObject) do - graphql_name 'TestTypeWithField' - options.reverse_merge!(null: true) - field :test_field, field_type, - authorize: field_authorizations, - **options - - define_method :test_field do - resolved_value - end - end - end - - def resolve - service.authorized_resolve[type_instance, {}, context] - end - - subject(:service) { described_class.new(field) } - - describe '#authorized_resolve' do - let_it_be(:current_user) { build(:user) } - let_it_be(:presented_object) { 'presented object' } - let_it_be(:query_type) { GraphQL::ObjectType.new } - let_it_be(:schema) { GitlabSchema } - let_it_be(:query) { GraphQL::Query.new(schema, document: nil, context: {}, variables: {}) } - let_it_be(:context) { GraphQL::Query::Context.new(query: query, values: { current_user: current_user }, object: nil) } - - let(:type_class) { type_with_field(custom_type, :read_field, presented_object) } - let(:type_instance) { type_class.authorized_new(presented_object, context) } - let(:field) { type_class.fields['testField'].to_graphql } - - subject(:resolved) { ::Gitlab::Graphql::Lazy.force(resolve) } - - context 'reading the field of a lazy value' do - let(:ability) { :read_field } - let(:presented_object) { lazy_upcase('a') } - let(:type_class) { type_with_field(GraphQL::STRING_TYPE, ability) } - - let(:upcaser) do - Module.new do - def self.upcase(strs) - strs.map(&:upcase) - end - end - end - - def lazy_upcase(str) - ::BatchLoader::GraphQL.for(str).batch do |strs, found| - strs.zip(upcaser.upcase(strs)).each { |s, us| found[s, us] } - end - end - - it 'does not run authorizations until we force the resolved value' do - expect(Ability).not_to receive(:allowed?) - - expect(resolve).to respond_to(:force) - end - - it 'runs authorizations when we force the resolved value' do - spy_ability_check_for(ability, 'A') - - expect(resolved).to eq('Resolved value') - end - - it 'redacts values that fail the permissions check' do - spy_ability_check_for(ability, 'A', passed: false) - - expect(resolved).to be_nil - end - - context 'we batch two calls' do - def resolve(value) - instance = type_class.authorized_new(lazy_upcase(value), context) - service.authorized_resolve[instance, {}, context] - end - - it 'batches resolution, but authorizes each object separately' do - expect(upcaser).to receive(:upcase).once.and_call_original - spy_ability_check_for(:read_field, 'A', passed: true) - spy_ability_check_for(:read_field, 'B', passed: false) - spy_ability_check_for(:read_field, 'C', passed: true) - - a = resolve('a') - b = resolve('b') - c = resolve('c') - - expect(a.force).to be_present - expect(b.force).to be_nil - expect(c.force).to be_present - end - end - end - - shared_examples 'authorizing fields' do - context 'scalar types' do - shared_examples 'checking permissions on the presented object' do - it 'checks the abilities on the object being presented and returns the value' do - expected_permissions.each do |permission| - spy_ability_check_for(permission, presented_object, passed: true) - end - - expect(resolved).to eq('Resolved value') - end - - it 'returns nil if the value was not authorized' do - allow(Ability).to receive(:allowed?).and_return false - - expect(resolved).to be_nil - end - end - - context 'when the field is a built-in scalar type' do - let(:type_class) { type_with_field(GraphQL::STRING_TYPE, :read_field) } - let(:expected_permissions) { [:read_field] } - - it_behaves_like 'checking permissions on the presented object' - end - - context 'when the field is a list of scalar types' do - let(:type_class) { type_with_field([GraphQL::STRING_TYPE], :read_field) } - let(:expected_permissions) { [:read_field] } - - it_behaves_like 'checking permissions on the presented object' - end - - context 'when the field is sub-classed scalar type' do - let(:type_class) { type_with_field(Types::TimeType, :read_field) } - let(:expected_permissions) { [:read_field] } - - it_behaves_like 'checking permissions on the presented object' - end - - context 'when the field is a list of sub-classed scalar types' do - let(:type_class) { type_with_field([Types::TimeType], :read_field) } - let(:expected_permissions) { [:read_field] } - - it_behaves_like 'checking permissions on the presented object' - end - end - - context 'when the field is a connection' do - context 'when it resolves to nil' do - let(:type_class) { type_with_field(Types::QueryType.connection_type, :read_field, nil) } - - it 'does not fail when authorizing' do - expect(resolved).to be_nil - end - end - - context 'when it returns values' do - let(:objects) { [1, 2, 3] } - let(:field_type) { type([:read_object]).connection_type } - let(:type_class) { type_with_field(field_type, [], objects) } - - it 'filters out unauthorized values' do - spy_ability_check_for(:read_object, 1, passed: true) - spy_ability_check_for(:read_object, 2, passed: false) - spy_ability_check_for(:read_object, 3, passed: true) - - expect(resolved.nodes).to eq [1, 3] - end - end - end - - context 'when the field is a specific type' do - let(:custom_type) { type(:read_type) } - let(:object_in_field) { double('presented in field') } - - let(:type_class) { type_with_field(custom_type, :read_field, object_in_field) } - let(:type_instance) { type_class.authorized_new(object_in_field, context) } - - it 'checks both field & type permissions' do - spy_ability_check_for(:read_field, object_in_field, passed: true) - spy_ability_check_for(:read_type, object_in_field, passed: true) - - expect(resolved).to eq(object_in_field) - end - - it 'returns nil if viewing was not allowed' do - spy_ability_check_for(:read_field, object_in_field, passed: false) - spy_ability_check_for(:read_type, object_in_field, passed: true) - - expect(resolved).to be_nil - end - - context 'when the field is not nullable' do - let(:type_class) { type_with_field(custom_type, :read_field, object_in_field, null: false) } - - it 'returns nil when viewing is not allowed' do - spy_ability_check_for(:read_type, object_in_field, passed: false) - - expect(resolved).to be_nil - end - end - - context 'when the field is a list' do - let(:object_1) { double('presented in field 1') } - let(:object_2) { double('presented in field 2') } - let(:presented_types) { [double(object: object_1), double(object: object_2)] } - - let(:type_class) { type_with_field([custom_type], :read_field, presented_types) } - let(:type_instance) { type_class.authorized_new(presented_types, context) } - - it 'checks all permissions' do - allow(Ability).to receive(:allowed?) { true } - - spy_ability_check_for(:read_field, object_1, passed: true) - spy_ability_check_for(:read_type, object_1, passed: true) - spy_ability_check_for(:read_field, object_2, passed: true) - spy_ability_check_for(:read_type, object_2, passed: true) - - expect(resolved).to eq(presented_types) - end - - it 'filters out objects that the user cannot see' do - allow(Ability).to receive(:allowed?) { true } - - spy_ability_check_for(:read_type, object_1, passed: false) - - expect(resolved).to contain_exactly(have_attributes(object: object_2)) - end - end - end - end - - it_behaves_like 'authorizing fields' - end - - private - - def spy_ability_check_for(ability, object, passed: true) - expect(Ability) - .to receive(:allowed?) - .with(current_user, ability, object) - .and_return(passed) - end -end diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index c5d7665c3b2..fb48d820057 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -22,6 +22,14 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do def current_user user end + + def context + { current_user: user } + end + + def self.authorization + @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(required_permissions) + end end end @@ -30,9 +38,16 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do subject(:loading_resource) { fake_class.new(user, project) } + before do + # don't allow anything by default + allow(Ability).to receive(:allowed?) do + false + end + end + context 'when the user is allowed to perform the action' do before do - allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do + allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project) do true end end @@ -48,24 +63,12 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do expect { loading_resource.authorize!(project) }.not_to raise_error end end - - describe '#authorized_resource?' do - it 'is true' do - expect(loading_resource.authorized_resource?(project)).to be(true) - end - end end context 'when the user is not allowed to perform the action' do - before do - allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do - false - end - end - describe '#authorized_find!' do it 'raises an error' do - expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect { loading_resource.authorized_find! }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end @@ -74,12 +77,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end - - describe '#authorized_resource?' do - it 'is false' do - expect(loading_resource.authorized_resource?(project)).to be(false) - end - end end context 'when the class does not define #find_object' do @@ -92,46 +89,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - context 'when the class does not define authorize' do - let(:fake_class) do - Class.new do - include Gitlab::Graphql::Authorize::AuthorizeResource - - attr_reader :user, :found_object - - def initialize(user, found_object) - @user, @found_object = user, found_object - end - - def find_object(*_args) - found_object - end - - def current_user - user - end - - def self.name - 'TestClass' - end - end - end - - let(:error) { /#{fake_class.name} has no authorizations/ } - - describe '#authorized_find!' do - it 'raises a comprehensive error message' do - expect { loading_resource.authorized_find! }.to raise_error(error) - end - end - - describe '#authorized_resource?' do - it 'raises a comprehensive error message' do - expect { loading_resource.authorized_resource?(project) }.to raise_error(error) - end - end - end - describe '#authorize' do it 'adds permissions from subclasses to those of superclasses when used on classes' do base_class = Class.new do |