auth_spec.rb 19.8 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1
2
require 'spec_helper'

3
describe Gitlab::Auth do
4
  let(:gl_auth) { described_class }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
5

6
7
  describe 'constants' do
    it 'API_SCOPES contains all scopes for API access' do
8
      expect(subject::API_SCOPES).to eq %i[api read_user sudo read_repository]
9
10
11
12
13
14
15
16
17
18
    end

    it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
      expect(subject::OPENID_SCOPES).to eq [:openid]
    end

    it 'DEFAULT_SCOPES contains all default scopes' do
      expect(subject::DEFAULT_SCOPES).to eq [:api]
    end

19
    it 'optional_scopes contains all non-default scopes' do
20
21
      stub_container_registry_config(enabled: true)

22
      expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
23
24
    end

25
    context 'registry_scopes' do
26
27
28
29
30
31
      context 'when registry is disabled' do
        before do
          stub_container_registry_config(enabled: false)
        end

        it 'is empty' do
32
          expect(subject.registry_scopes).to eq []
33
34
35
36
37
38
39
40
41
        end
      end

      context 'when registry is enabled' do
        before do
          stub_container_registry_config(enabled: true)
        end

        it 'contains all registry related scopes' do
42
          expect(subject.registry_scopes).to eq %i[read_registry]
43
44
        end
      end
45
46
47
    end
  end

48
  describe 'find_for_git_client' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
49
50
51
52
53
54
55
56
57
58
59
60
    context 'build token' do
      subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }

      context 'for running build' do
        let!(:build) { create(:ci_build, :running) }
        let(:project) { build.project }

        before do
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
        end

        it 'recognises user-less build' do
61
          expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
Kamil Trzcinski's avatar
Kamil Trzcinski committed
62
63
64
65
66
        end

        it 'recognises user token' do
          build.update(user: create(:user))

67
          expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
Kamil Trzcinski's avatar
Kamil Trzcinski committed
68
69
70
        end
      end

71
      (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
72
73
74
75
76
77
78
79
80
        context "for #{build_status} build" do
          let!(:build) { create(:ci_build, status: build_status) }
          let(:project) { build.project }

          before do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
          end

          it 'denies authentication' do
81
            expect(subject).to eq(Gitlab::Auth::Result.new)
82
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
83
84
85
86
87
        end
      end
    end

    it 'recognizes other ci services' do
88
      project = create(:project)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
89
90
      project.create_drone_ci_service(active: true)
      project.drone_ci_service.update(token: 'token')
Felipe Artur's avatar
Felipe Artur committed
91

92
93
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token')
      expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
94
95
96
97
98
    end

    it 'recognizes master passwords' do
      user = create(:user, password: 'password')

99
100
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
101
102
    end

Pawel Chojnacki's avatar
Pawel Chojnacki committed
103
104
    include_examples 'user login operation with unique ip limit' do
      let(:user) { create(:user, password: 'password') }
105

Pawel Chojnacki's avatar
Pawel Chojnacki committed
106
      def operation
107
108
109
110
        expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end
    end

111
112
113
114
    context 'while using LFS authenticate' do
      it 'recognizes user lfs tokens' do
        user = create(:user)
        token = Gitlab::LfsToken.new(user).token
115

116
117
118
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
        expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
      end
119

120
121
122
      it 'recognizes deploy key lfs tokens' do
        key = create(:deploy_key)
        token = Gitlab::LfsToken.new(key).token
123

124
125
126
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
      end
127

128
      it 'does not try password auth before oauth' do
129
        user = create(:user)
130
131
132
        token = Gitlab::LfsToken.new(user).token

        expect(gl_auth).not_to receive(:find_with_user_password)
133

134
135
        gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
      end
136
137
138

      it 'grants deploy key write permissions' do
        project = create(:project)
139
140
        key = create(:deploy_key)
        create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
141
142
143
144
145
146
147
148
        token = Gitlab::LfsToken.new(key).token

        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_write_authentication_abilities))
      end

      it 'does not grant deploy key write permissions' do
        project = create(:project)
149
        key = create(:deploy_key)
150
151
152
153
154
        token = Gitlab::LfsToken.new(key).token

        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
      end
155
156
157
158
159
160
161
162
    end

    context 'while using OAuth tokens as passwords' do
      let(:user) { create(:user) }
      let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
      let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }

      it 'succeeds for OAuth tokens with the `api` scope' do
163
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
164
        expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities))
165
166
167
      end

      it 'fails for OAuth tokens with other scopes' do
168
        token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
169

170
171
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
        expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
172
      end
173
174
175
176
177
178

      it 'does not try password auth before oauth' do
        expect(gl_auth).not_to receive(:find_with_user_password)

        gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
      end
179
180
    end

181
182
    context 'while using personal access tokens as passwords' do
      it 'succeeds for personal access tokens with the `api` scope' do
Simon Vocella's avatar
Simon Vocella committed
183
184
185
        personal_access_token = create(:personal_access_token, scopes: ['api'])

        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
186
        expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, full_authentication_abilities))
187
188
      end

189
190
191
192
193
194
195
      context 'when registry is enabled' do
        before do
          stub_container_registry_config(enabled: true)
        end

        it 'succeeds for personal access tokens with the `read_registry` scope' do
          personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
196

197
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
198
          expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, [:read_project, :build_download_code, :build_read_container_image]))
199
        end
200
201
      end

Simon Vocella's avatar
Simon Vocella committed
202
      it 'succeeds if it is an impersonation token' do
203
        impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
204

Simon Vocella's avatar
Simon Vocella committed
205
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
206
        expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_access_token, full_authentication_abilities))
207
208
      end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
209
      it 'limits abilities based on scope' do
210
        personal_access_token = create(:personal_access_token, scopes: %w[read_user sudo])
211

Z.J. van de Weg's avatar
Z.J. van de Weg committed
212
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
213
        expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, []))
214
      end
215

Simon Vocella's avatar
Simon Vocella committed
216
217
218
      it 'fails if password is nil' do
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
        expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
219
220
221
222
223
224
225
226
      end
    end

    context 'while using regular user and password' do
      it 'falls through lfs authentication' do
        user = create(
          :user,
          username: 'normal_user',
227
          password: 'my-secret'
228
229
230
231
232
233
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end

234
      it 'fails through oauth authentication when the username is oauth2' do
235
236
237
        user = create(
          :user,
          username: 'oauth2',
238
          password: 'my-secret'
239
240
241
242
243
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end
244
245
246
247
248
    end

    it 'returns double nil for invalid credentials' do
      login = 'foo'

249
250
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
      expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new)
251
    end
252
253

    it 'throws an error suggesting user create a PAT when internal auth is disabled' do
254
      allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false }
255

256
      expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
257
    end
258
259
260
261
262

    context 'while using deploy tokens' do
      let(:project) { create(:project) }
      let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }

263
264
      context 'when the deploy token has read_repository as scope' do
        let(:deploy_token) { create(:deploy_token, :read_repository, project: project) }
265

266
        it 'succeeds when project is present, token is valid and has read_repository as scope' do
267
268
          abilities = %i(read_project download_code)
          auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, abilities)
269

270
271
272
273
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
          expect(gl_auth.find_for_git_client('', deploy_token.token, project: project, ip: 'ip'))
            .to eq(auth_success)
        end
274

275
276
277
278
279
        it 'fails if token is nil' do
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
          expect(gl_auth.find_for_git_client('', nil, project: project, ip: 'ip'))
            .to eq(auth_failure)
        end
280

281
282
283
284
285
        it 'fails if token is not related to project' do
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
          expect(gl_auth.find_for_git_client('', 'abcdef', project: project, ip: 'ip'))
            .to eq(auth_failure)
        end
286

287
288
289
290
291
        it 'fails for any other project' do
          another_project = create(:project)
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
          expect(gl_auth.find_for_git_client('', deploy_token.token, project: another_project, ip: 'ip'))
            .to eq(auth_failure)
292
293
        end

294
295
        it 'fails if token has been revoked' do
          deploy_token.revoke!
296

297
298
299
300
          expect(deploy_token.revoked?).to be_truthy
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
          expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip'))
            .to eq(auth_failure)
301
302
303
        end
      end

304
305
306
307
308
309
310
311
312
      context 'when the deploy token has read_registry as a scope' do
        let(:deploy_token) { create(:deploy_token, :read_registry, project: project) }

        context 'when registry enabled' do
          before do
            stub_container_registry_config(enabled: true)
          end

          it 'succeeds if deploy token does have read_registry as scope' do
313
            abilities = %i(read_project build_download_code build_read_container_image)
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
            auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, abilities)

            expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
            expect(gl_auth.find_for_git_client('', deploy_token.token, project: nil, ip: 'ip'))
              .to eq(auth_success)
          end

          it 'fails if token is nil' do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
            expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end

          it 'fails if token is not related to project' do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
            expect(gl_auth.find_for_git_client('', 'abcdef', project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end

          it 'fails if token has been revoked' do
            deploy_token.revoke!

            expect(deploy_token.revoked?).to be_truthy
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
            expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end
341
342
        end

343
344
345
346
        context 'when registry disabled' do
          before do
            stub_container_registry_config(enabled: false)
          end
347

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
          it 'fails if deploy token have read_registry as scope' do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
            expect(gl_auth.find_for_git_client('', deploy_token.token, project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end

          it 'fails if token is nil' do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
            expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end

          it 'fails if token is not related to project' do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
            expect(gl_auth.find_for_git_client('', 'abcdef', project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end

          it 'fails if token has been revoked' do
            deploy_token.revoke!

            expect(deploy_token.revoked?).to be_truthy
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token')
            expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip'))
              .to eq(auth_failure)
          end
374
375
        end
      end
376
    end
377
378
  end

379
  describe 'find_with_user_password' do
380
381
382
383
384
    let!(:user) do
      create(:user,
        username: username,
        password: password,
        password_confirmation: password)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
385
    end
386
    let(:username) { 'John' }     # username isn't lowercase, test this
387
    let(:password) { 'my-secret' }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
388

389
    it "finds user by valid login/password" do
390
      expect( gl_auth.find_with_user_password(username, password) ).to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
391
392
    end

393
    it 'finds user by valid email/password with case-insensitive email' do
394
      expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
395
396
    end

397
    it 'finds user by valid username/password with case-insensitive username' do
398
      expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
399
400
    end

401
    it "does not find user with invalid password" do
402
      password = 'wrong'
403
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
404
405
    end

406
    it "does not find user with invalid login" do
407
      user = 'wrong'
408
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
409
    end
410

Pawel Chojnacki's avatar
Pawel Chojnacki committed
411
412
    include_examples 'user login operation with unique ip limit' do
      def operation
413
        expect(gl_auth.find_with_user_password(username, password)).to eq(user)
Pawel Chojnacki's avatar
Pawel Chojnacki committed
414
415
416
      end
    end

417
418
419
420
421
422
423
424
425
426
427
428
    it "does not find user in blocked state" do
      user.block

      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
    end

    it "does not find user in ldap_blocked state" do
      user.ldap_block

      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
    end

429
    context "with ldap enabled" do
430
      before do
431
        allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
432
      end
433
434

      it "tries to autheticate with db before ldap" do
435
        expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login)
436

437
438
439
440
441
442
443
        expect(gl_auth.find_with_user_password(username, password)).to eq(user)
      end

      it "does not find user by using ldap as fallback to for authentication" do
        expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)

        expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil
444
445
      end

446
447
      it "find new user by using ldap as fallback to for authentication" do
        expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(user)
448

449
        expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user)
450
451
      end
    end
452
453
454
455
456
457
458
459
460
461
462
463

    context "with password authentication disabled for Git" do
      before do
        stub_application_setting(password_authentication_enabled_for_git: false)
      end

      it "does not find user by valid login/password" do
        expect(gl_auth.find_with_user_password(username, password)).to be_nil
      end

      context "with ldap enabled" do
        before do
464
          allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
465
466
467
468
469
470
471
        end

        it "does not find non-ldap user by valid login/password" do
          expect(gl_auth.find_with_user_password(username, password)).to be_nil
        end
      end
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
472
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
473
474
475

  private

476
  def build_authentication_abilities
Kamil Trzcinski's avatar
Kamil Trzcinski committed
477
478
479
    [
      :read_project,
      :build_download_code,
480
      :build_read_container_image,
Kamil Trzcinski's avatar
Kamil Trzcinski committed
481
482
483
484
      :build_create_container_image
    ]
  end

485
  def read_authentication_abilities
Kamil Trzcinski's avatar
Kamil Trzcinski committed
486
487
488
489
490
491
492
    [
      :read_project,
      :download_code,
      :read_container_image
    ]
  end

493
  def read_write_authentication_abilities
494
    read_authentication_abilities + [
Kamil Trzcinski's avatar
Kamil Trzcinski committed
495
      :push_code,
496
497
498
499
500
501
      :create_container_image
    ]
  end

  def full_authentication_abilities
    read_write_authentication_abilities + [
502
      :admin_container_image
Kamil Trzcinski's avatar
Kamil Trzcinski committed
503
504
    ]
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
505
end