summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
blob: d60d1b7559a3f805d9e2ea255edec348f781f6f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true

require 'spec_helper'

# Also see spec/graphql/features/authorization_spec.rb for
# integration tests of AuthorizeFieldService
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,
            resolve: -> (_, _, _) { resolved_value },
            **options
    end
  end

  let(:current_user) { double(:current_user) }
  subject(:service) { described_class.new(field) }

  describe '#authorized_resolve' do
    let(:presented_object) { double('presented object') }
    let(:presented_type) { double('parent type', object: presented_object) }
    subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }

    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 wasn't 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(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields['testField'].to_graphql }
        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(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields['testField'].to_graphql }
        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(:field) { type_with_field(Types::TimeType, :read_field).fields['testField'].to_graphql }
        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(:field) { type_with_field([Types::TimeType], :read_field).fields['testField'].to_graphql }
        let(:expected_permissions) { [:read_field] }

        it_behaves_like 'checking permissions on the presented object'
      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(:field) { type_with_field(custom_type, :read_field, object_in_field).fields['testField'].to_graphql }

      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(:field) { type_with_field(custom_type, [], object_in_field, null: false).fields['testField'].to_graphql }

        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(:field) { type_with_field([custom_type], :read_field, presented_types).fields['testField'].to_graphql }

        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.map(&:object)).to contain_exactly(object_2)
        end
      end
    end
  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