1# frozen_string_literal: true
2
3require 'spec_helper'
4
5RSpec.describe DeployToken do
6  subject(:deploy_token) { create(:deploy_token) }
7
8  it { is_expected.to have_many :project_deploy_tokens }
9  it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
10  it { is_expected.to have_many :group_deploy_tokens }
11  it { is_expected.to have_many(:groups).through(:group_deploy_tokens) }
12
13  it_behaves_like 'having unique enum values'
14
15  describe 'validations' do
16    let(:username_format_message) { "can contain only letters, digits, '_', '-', '+', and '.'" }
17
18    it { is_expected.to validate_length_of(:username).is_at_most(255) }
19    it { is_expected.to allow_value('GitLab+deploy_token-3.14').for(:username) }
20    it { is_expected.not_to allow_value('<script>').for(:username).with_message(username_format_message) }
21    it { is_expected.not_to allow_value('').for(:username).with_message(username_format_message) }
22    it { is_expected.to validate_presence_of(:deploy_token_type) }
23  end
24
25  shared_examples 'invalid group deploy token' do
26    context 'revoked' do
27      before do
28        deploy_token.update_column(:revoked, true)
29      end
30
31      it { is_expected.to eq(false) }
32    end
33
34    context 'expired' do
35      before do
36        deploy_token.update!(expires_at: Date.today - 1.month)
37      end
38
39      it { is_expected.to eq(false) }
40    end
41
42    context 'project type' do
43      before do
44        deploy_token.update_column(:deploy_token_type, 2)
45      end
46
47      it { is_expected.to eq(false) }
48    end
49  end
50
51  describe 'deploy_token_type validations' do
52    context 'when a deploy token is associated to a group' do
53      it 'does not allow setting a project to it' do
54        group_token = create(:deploy_token, :group)
55        group_token.projects << build(:project)
56
57        expect(group_token).not_to be_valid
58        expect(group_token.errors.full_messages).to include('Deploy token cannot have projects assigned')
59      end
60    end
61
62    context 'when a deploy token is associated to a project' do
63      it 'does not allow setting a group to it' do
64        project_token = create(:deploy_token)
65        project_token.groups << build(:group)
66
67        expect(project_token).not_to be_valid
68        expect(project_token.errors.full_messages).to include('Deploy token cannot have groups assigned')
69      end
70    end
71  end
72
73  describe '#ensure_token' do
74    it 'ensures a token' do
75      deploy_token.token = nil
76      deploy_token.save!
77
78      expect(deploy_token.token).not_to be_empty
79    end
80  end
81
82  describe '#ensure_at_least_one_scope' do
83    context 'with at least one scope' do
84      it 'is valid' do
85        is_expected.to be_valid
86      end
87    end
88
89    context 'with no scopes' do
90      it 'is invalid' do
91        deploy_token = build(:deploy_token, read_repository: false, read_registry: false, write_registry: false)
92
93        expect(deploy_token).not_to be_valid
94        expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank")
95      end
96    end
97  end
98
99  describe '#valid_for_dependency_proxy?' do
100    let_it_be_with_reload(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
101
102    subject { deploy_token.valid_for_dependency_proxy? }
103
104    it { is_expected.to eq(true) }
105
106    it_behaves_like 'invalid group deploy token'
107
108    context 'insufficient scopes' do
109      before do
110        deploy_token.update_column(:write_registry, false)
111      end
112
113      it { is_expected.to eq(false) }
114    end
115  end
116
117  describe '#has_access_to_group?' do
118    let_it_be(:group) { create(:group) }
119    let_it_be_with_reload(:deploy_token) { create(:deploy_token, :group) }
120    let_it_be(:group_deploy_token) { create(:group_deploy_token, group: group, deploy_token: deploy_token) }
121
122    let(:test_group) { group }
123
124    subject { deploy_token.has_access_to_group?(test_group) }
125
126    it { is_expected.to eq(true) }
127
128    it_behaves_like 'invalid group deploy token'
129
130    context 'for a sub group' do
131      let(:test_group) { create(:group, parent: group) }
132
133      it { is_expected.to eq(true) }
134    end
135
136    context 'for a different group' do
137      let(:test_group) { create(:group) }
138
139      it { is_expected.to eq(false) }
140    end
141  end
142
143  describe '#scopes' do
144    context 'with all the scopes' do
145      let_it_be(:deploy_token) { create(:deploy_token, :all_scopes) }
146
147      it 'returns scopes assigned to DeployToken' do
148        expect(deploy_token.scopes).to eq(DeployToken::AVAILABLE_SCOPES)
149      end
150    end
151
152    context 'with only one scope' do
153      it 'returns scopes assigned to DeployToken' do
154        deploy_token = create(:deploy_token, read_registry: false, write_registry: false)
155        expect(deploy_token.scopes).to eq([:read_repository])
156      end
157    end
158  end
159
160  describe '#revoke!' do
161    it 'updates revoke attribute' do
162      deploy_token.revoke!
163      expect(deploy_token.revoked?).to be_truthy
164    end
165  end
166
167  describe "#active?" do
168    context "when it has been revoked" do
169      it 'returns false' do
170        deploy_token.revoke!
171        expect(deploy_token.active?).to be_falsy
172      end
173    end
174
175    context "when it hasn't been revoked and is not expired" do
176      it 'returns true' do
177        expect(deploy_token.active?).to be_truthy
178      end
179    end
180
181    context "when it hasn't been revoked and is expired" do
182      it 'returns true' do
183        deploy_token.update_attribute(:expires_at, Date.today - 5.days)
184        expect(deploy_token.active?).to be_falsy
185      end
186    end
187
188    context "when it hasn't been revoked and has no expiry" do
189      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
190
191      it 'returns true' do
192        expect(deploy_token.active?).to be_truthy
193      end
194    end
195  end
196
197  # override the default PolicyActor implementation that always returns false
198  describe "#deactivated?" do
199    context "when it has been revoked" do
200      it 'returns true' do
201        deploy_token.revoke!
202
203        expect(deploy_token.deactivated?).to be_truthy
204      end
205    end
206
207    context "when it hasn't been revoked and is not expired" do
208      it 'returns false' do
209        expect(deploy_token.deactivated?).to be_falsy
210      end
211    end
212
213    context "when it hasn't been revoked and is expired" do
214      it 'returns false' do
215        deploy_token.update_attribute(:expires_at, Date.today - 5.days)
216
217        expect(deploy_token.deactivated?).to be_truthy
218      end
219    end
220
221    context "when it hasn't been revoked and has no expiry" do
222      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
223
224      it 'returns false' do
225        expect(deploy_token.deactivated?).to be_falsy
226      end
227    end
228  end
229
230  describe '#username' do
231    context 'persisted records' do
232      it 'returns a default username if none is set' do
233        expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
234      end
235
236      it 'returns the username provided if one is set' do
237        deploy_token = create(:deploy_token, username: 'deployer')
238
239        expect(deploy_token.username).to eq('deployer')
240      end
241    end
242
243    context 'new records' do
244      it 'returns nil if no username is set' do
245        deploy_token = build(:deploy_token)
246
247        expect(deploy_token.username).to be_nil
248      end
249
250      it 'returns the username provided if one is set' do
251        deploy_token = build(:deploy_token, username: 'deployer')
252
253        expect(deploy_token.username).to eq('deployer')
254      end
255    end
256  end
257
258  describe '#holder' do
259    subject { deploy_token.holder }
260
261    context 'when the token is of project type' do
262      it 'returns the relevant holder token' do
263        expect(subject).to eq(deploy_token.project_deploy_tokens.first)
264      end
265    end
266
267    context 'when the token is of group type' do
268      let(:group) { create(:group) }
269      let(:deploy_token) { create(:deploy_token, :group) }
270
271      it 'returns the relevant holder token' do
272        expect(subject).to eq(deploy_token.group_deploy_tokens.first)
273      end
274    end
275  end
276
277  describe '#has_access_to?' do
278    let(:project) { create(:project) }
279
280    subject { deploy_token.has_access_to?(project) }
281
282    context 'when a project is not passed in' do
283      let(:project) { nil }
284
285      it { is_expected.to be_falsy }
286    end
287
288    context 'when a project is passed in' do
289      context 'when deploy token is active and related to project' do
290        let(:deploy_token) { create(:deploy_token, projects: [project]) }
291
292        it { is_expected.to be_truthy }
293      end
294
295      context 'when deploy token is active but not related to project' do
296        let(:deploy_token) { create(:deploy_token) }
297
298        it { is_expected.to be_falsy }
299      end
300
301      context 'when deploy token is revoked and related to project' do
302        let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) }
303
304        it { is_expected.to be_falsy }
305      end
306
307      context 'when deploy token is revoked and not related to the project' do
308        let(:deploy_token) { create(:deploy_token, :revoked) }
309
310        it { is_expected.to be_falsy }
311      end
312
313      context 'and when the token is of group type' do
314        let_it_be(:group) { create(:group) }
315
316        let(:deploy_token) { create(:deploy_token, :group) }
317
318        before do
319          deploy_token.groups << group
320        end
321
322        context 'and the passed-in project does not belong to any group' do
323          it { is_expected.to be_falsy }
324        end
325
326        context 'and the passed-in project belongs to the token group' do
327          it 'is true' do
328            group.projects << project
329
330            is_expected.to be_truthy
331          end
332        end
333
334        context 'and the passed-in project belongs to a subgroup' do
335          let(:child_group) { create(:group, parent_id: group.id) }
336          let(:grandchild_group) { create(:group, parent_id: child_group.id) }
337
338          before do
339            grandchild_group.projects << project
340          end
341
342          context 'and the token group is an ancestor (grand-parent) of this group' do
343            it { is_expected.to be_truthy }
344          end
345
346          context 'and the token group is not ancestor of this group' do
347            let(:child2_group) { create(:group, parent_id: group.id) }
348
349            it 'is false' do
350              deploy_token.groups = [child2_group]
351
352              is_expected.to be_falsey
353            end
354          end
355        end
356
357        context 'and the passed-in project does not belong to the token group' do
358          it { is_expected.to be_falsy }
359        end
360
361        context 'and the project belongs to a group that is parent of the token group' do
362          let(:super_group) { create(:group) }
363          let(:deploy_token) { create(:deploy_token, :group) }
364          let(:group) { create(:group, parent_id: super_group.id) }
365
366          it 'is false' do
367            super_group.projects << project
368
369            is_expected.to be_falsey
370          end
371        end
372      end
373
374      context 'and the token is of project type' do
375        let(:deploy_token) { create(:deploy_token, projects: [project]) }
376
377        context 'and the passed-in project is the same as the token project' do
378          it { is_expected.to be_truthy }
379        end
380
381        context 'and the passed-in project is not the same as the token project' do
382          subject { deploy_token.has_access_to?(create(:project)) }
383
384          it { is_expected.to be_falsey }
385        end
386      end
387    end
388  end
389
390  describe '#expires_at' do
391    context 'when using Forever.date' do
392      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
393
394      it 'returns nil' do
395        expect(deploy_token.expires_at).to be_nil
396      end
397    end
398
399    context 'when using a personalized date' do
400      let(:expires_at) { Date.today + 5.months }
401      let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
402
403      it 'returns the personalized date' do
404        expect(deploy_token.expires_at).to eq(expires_at)
405      end
406    end
407  end
408
409  describe '#expires_at=' do
410    context 'when passing nil' do
411      let(:deploy_token) { create(:deploy_token, expires_at: nil) }
412
413      it 'assigns Forever.date' do
414        expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date)
415      end
416    end
417
418    context 'when passing a value' do
419      let(:expires_at) { Date.today + 5.months }
420      let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
421
422      it 'respects the value' do
423        expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at)
424      end
425    end
426  end
427
428  describe '.gitlab_deploy_token' do
429    let(:project) { create(:project ) }
430
431    subject { project.deploy_tokens.gitlab_deploy_token }
432
433    context 'with a gitlab deploy token associated' do
434      it 'returns the gitlab deploy token' do
435        deploy_token = create(:deploy_token, :gitlab_deploy_token, projects: [project])
436        is_expected.to eq(deploy_token)
437      end
438    end
439
440    context 'with no gitlab deploy token associated' do
441      it 'returns nil' do
442        is_expected.to be_nil
443      end
444    end
445  end
446
447  describe '#accessible_projects' do
448    subject { deploy_token.accessible_projects }
449
450    context 'when a deploy token is associated to a project' do
451      let_it_be(:deploy_token) { create(:deploy_token, :project) }
452
453      it 'returns only projects directly associated with the token' do
454        expect(deploy_token).to receive(:projects)
455
456        subject
457      end
458    end
459
460    context 'when a deploy token is associated to a group' do
461      let_it_be(:group) { create(:group) }
462      let_it_be(:deploy_token) { create(:deploy_token, :group, groups: [group]) }
463
464      it 'returns all projects from the group' do
465        expect(group).to receive(:all_projects)
466
467        subject
468      end
469    end
470  end
471end
472