auth_spec.rb 38.4 KB
Newer Older
1 2
# frozen_string_literal: true

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
3 4
require 'spec_helper'

5
RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
6
  let_it_be(:project) { create(:project) }
7

8
  let(:auth_failure) { { actor: nil, project: nil, type: nil, authentication_abilities: nil } }
9
  let(:gl_auth) { described_class }
10

11 12
  describe 'constants' do
    it 'API_SCOPES contains all scopes for API access' do
13
      expect(subject::API_SCOPES).to eq %i[api read_user read_api]
14 15 16 17 18 19 20 21
    end

    it 'ADMIN_SCOPES contains all scopes for ADMIN access' do
      expect(subject::ADMIN_SCOPES).to eq %i[sudo]
    end

    it 'REPOSITORY_SCOPES contains all scopes for REPOSITORY access' do
      expect(subject::REPOSITORY_SCOPES).to eq %i[read_repository write_repository]
22 23 24 25 26 27 28
    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
29
      expect(subject::DEFAULT_SCOPES).to eq [:api]
30 31
    end

32
    it 'optional_scopes contains all non-default scopes' do
33 34
      stub_container_registry_config(enabled: true)

35
      expect(subject.optional_scopes).to eq %i[read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email]
36 37 38 39 40 41 42
    end
  end

  context 'available_scopes' do
    it 'contains all non-default scopes' do
      stub_container_registry_config(enabled: true)

43
      expect(subject.all_available_scopes).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo]
44 45 46 47 48 49
    end

    it 'contains for non-admin user all non-default scopes without ADMIN access' do
      stub_container_registry_config(enabled: true)
      user = create(:user, admin: false)

50
      expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry]
51 52 53 54 55 56
    end

    it 'contains for admin user all non-default scopes with ADMIN access' do
      stub_container_registry_config(enabled: true)
      user = create(:user, admin: true)

57
      expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
58 59
    end

60
    context 'registry_scopes' do
61 62 63 64 65 66
      context 'when registry is disabled' do
        before do
          stub_container_registry_config(enabled: false)
        end

        it 'is empty' do
67
          expect(subject.registry_scopes).to eq []
68 69 70 71 72 73 74 75 76
        end
      end

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

        it 'contains all registry related scopes' do
77
          expect(subject.registry_scopes).to eq %i[read_registry write_registry]
78 79
        end
      end
80 81 82
    end
  end

83
  describe 'find_for_git_client' do
84 85 86 87 88 89
    describe 'rate limiting' do
      before do
        stub_rack_attack_setting(enabled: true, ip_whitelist: [])
      end

      context 'when IP is already banned' do
Zhu Shung's avatar
Zhu Shung committed
90
        subject { gl_auth.find_for_git_client('username', Gitlab::Password.test_default, project: nil, ip: 'ip') }
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

        before do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).to receive(:banned?).and_return(true)
          end
        end

        it 'raises an IpBlacklisted exception' do
          expect { subject }.to raise_error(Gitlab::Auth::IpBlacklisted)
        end
      end

      context 'for CI registry user' do
        let_it_be(:build) { create(:ci_build, :running) }

        it 'skips rate limiting for successful auth' do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).not_to receive(:reset!)
          end

          gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: 'ip')
        end

        it 'skips rate limiting for failed auth' do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).not_to receive(:register_fail!)
          end

          gl_auth.find_for_git_client('gitlab-ci-token', 'wrong_token', project: build.project, ip: 'ip')
        end
      end

      context 'for other users' do
        let_it_be(:user) { create(:user) }

        it 'resets rate limit for successful auth' do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).to receive(:reset!)
          end

          gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')
        end

134 135 136 137
        it 'rate limits a user by unique IPs' do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).to receive(:reset!)
          end
138
          expect(Gitlab::Auth::UniqueIpsLimiter).to receive(:limit_user!).twice.and_call_original
139 140 141 142

          gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')
        end

143 144 145 146 147 148 149 150 151 152
        it 'registers failure for failed auth' do
          expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
            expect(rate_limiter).to receive(:register_fail!)
          end

          gl_auth.find_for_git_client(user.username, 'wrong_password', project: nil, ip: 'ip')
        end
      end
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
153
    context 'build token' do
154 155 156
      subject { gl_auth.find_for_git_client(username, build.token, project: project, ip: 'ip') }

      let(:username) { 'gitlab-ci-token' }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
157 158 159 160 161 162

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

        it 'recognises user-less build' do
163
          expect(subject).to have_attributes(actor: nil, project: build.project, type: :ci, authentication_abilities: described_class.build_authentication_abilities)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
164 165 166 167 168
        end

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

169
          expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
170
        end
171 172 173 174

        it 'fails with blocked user token' do
          build.update(user: create(:user, :blocked))

175
          expect(subject).to have_attributes(auth_failure)
176
        end
177 178 179 180 181

        context 'username is not gitlab-ci-token' do
          let(:username) { 'another_username' }

          it 'fails to authenticate' do
182
            expect(subject).to have_attributes(auth_failure)
183 184
          end
        end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
185 186
      end

187
      (Ci::HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
188 189 190 191 192
        context "for #{build_status} build" do
          let!(:build) { create(:ci_build, status: build_status) }
          let(:project) { build.project }

          it 'denies authentication' do
193
            expect(subject).to have_attributes(auth_failure)
194
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
195 196 197 198 199
        end
      end
    end

    it 'recognizes other ci services' do
200
      project.create_drone_ci_integration(active: true)
201
      project.drone_ci_integration.update(token: 'token')
202

203
      expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to have_attributes(actor: nil, project: project, type: :ci, authentication_abilities: described_class.build_authentication_abilities)
204 205 206
    end

    it 'recognizes master passwords' do
Zhu Shung's avatar
Zhu Shung committed
207
      user = create(:user, password: Gitlab::Password.test_default)
208

Zhu Shung's avatar
Zhu Shung committed
209
      expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
210 211
    end

Pawel Chojnacki's avatar
Pawel Chojnacki committed
212
    include_examples 'user login operation with unique ip limit' do
Zhu Shung's avatar
Zhu Shung committed
213
      let(:user) { create(:user, password: Gitlab::Password.test_default) }
214

Pawel Chojnacki's avatar
Pawel Chojnacki committed
215
      def operation
Zhu Shung's avatar
Zhu Shung committed
216
        expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
217 218 219
      end
    end

220 221 222 223
    context 'while using LFS authenticate' do
      it 'recognizes user lfs tokens' do
        user = create(:user)
        token = Gitlab::LfsToken.new(user).token
224

225
        expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :lfs_token, authentication_abilities: described_class.read_write_project_authentication_abilities)
226
      end
227

228 229 230
      it 'recognizes deploy key lfs tokens' do
        key = create(:deploy_key)
        token = Gitlab::LfsToken.new(key).token
231

232
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities)
233
      end
234

235
      it 'does not try password auth before oauth' do
236
        user = create(:user)
237 238 239
        token = Gitlab::LfsToken.new(user).token

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

241 242
        gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
      end
243 244

      it 'grants deploy key write permissions' do
245 246
        key = create(:deploy_key)
        create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
247 248
        token = Gitlab::LfsToken.new(key).token

249
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_write_authentication_abilities)
250 251 252
      end

      it 'does not grant deploy key write permissions' do
253
        key = create(:deploy_key)
254 255
        token = Gitlab::LfsToken.new(key).token

256
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities)
257
      end
258 259 260 261 262 263
    end

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

264 265
      shared_examples 'an oauth failure' do
        it 'fails' do
266 267 268
          access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api')

          expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip'))
269
            .to have_attributes(auth_failure)
270 271 272
        end
      end

273 274 275 276 277 278 279 280 281 282 283 284 285 286
      context 'with specified scopes' do
        using RSpec::Parameterized::TableSyntax

        where(:scopes, :abilities) do
          'api' | described_class.full_authentication_abilities
          'read_api' | described_class.read_only_authentication_abilities
          'read_repository' | [:download_code]
          'write_repository' | [:download_code, :push_code]
          'read_user' | []
          'sudo' | []
          'openid' | []
          'profile' | []
          'email' | []
        end
287

288 289 290
        with_them do
          it 'authenticates with correct abilities' do
            access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: scopes)
291

292 293 294 295
            expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip'))
              .to have_attributes(actor: user, project: nil, type: :oauth, authentication_abilities: abilities)
          end
        end
296
      end
297 298

      it 'does not try password auth before oauth' do
299 300
        access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api')

301 302
        expect(gl_auth).not_to receive(:find_with_user_password)

303
        gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip')
304
      end
305 306 307 308

      context 'blocked user' do
        let(:user) { create(:user, :blocked) }

309 310 311 312 313 314
        it_behaves_like 'an oauth failure'
      end

      context 'orphaned token' do
        before do
          user.destroy
315
        end
316 317

        it_behaves_like 'an oauth failure'
318
      end
319 320
    end

321 322
    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
323 324
        personal_access_token = create(:personal_access_token, scopes: ['api'])

325
        expect_results_with_abilities(personal_access_token, described_class.full_authentication_abilities)
326 327 328 329 330 331 332 333 334 335 336 337
      end

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

        expect_results_with_abilities(personal_access_token, [:download_code])
      end

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

        expect_results_with_abilities(personal_access_token, [:download_code, :push_code])
338 339
      end

340 341 342 343 344 345 346
      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'])
347

348
          expect_results_with_abilities(personal_access_token, [:read_container_image])
349
        end
350 351
      end

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

355
        expect_results_with_abilities(impersonation_token, described_class.full_authentication_abilities)
356 357
      end

358 359 360 361 362 363
      it 'fails if it is an impersonation token but impersonation is blocked' do
        stub_config_setting(impersonation_enabled: false)

        impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])

        expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip'))
364
          .to have_attributes(auth_failure)
365 366
      end

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

370
        expect_results_with_abilities(personal_access_token, [])
371
      end
372

Simon Vocella's avatar
Simon Vocella committed
373
      it 'fails if password is nil' do
374
        expect_results_with_abilities(nil, nil, false)
375
      end
376 377 378 379 380 381 382 383 384 385 386

      context 'when user is blocked' do
        let(:user) { create(:user, :blocked) }
        let(:personal_access_token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }

        before do
          stub_container_registry_config(enabled: true)
        end

        it 'fails if user is blocked' do
          expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip'))
387
            .to have_attributes(auth_failure)
388 389
        end
      end
390

391 392
      context 'when using a resource access token' do
        shared_examples 'with a valid access token' do
393
          it 'successfully authenticates the project bot' do
394
            expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip'))
395
              .to have_attributes(actor: project_bot_user, project: nil, type: :personal_access_token, authentication_abilities: described_class.full_authentication_abilities)
396
          end
397 398

          it 'successfully authenticates the project bot with a nil project' do
399
            expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: nil, ip: 'ip'))
400
              .to have_attributes(actor: project_bot_user, project: nil, type: :personal_access_token, authentication_abilities: described_class.full_authentication_abilities)
401
          end
402 403
        end

404 405 406
        shared_examples 'with an invalid access token' do
          it 'fails for a non-member' do
            expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip'))
407
              .to have_attributes(auth_failure)
408 409 410 411 412 413 414 415
          end

          context 'when project bot user is blocked' do
            before do
              project_bot_user.block!
            end

            it 'fails for a blocked project bot' do
416
              expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip'))
417
                .to have_attributes(auth_failure)
418
            end
419 420
          end
        end
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436

        context 'when using a personal namespace project access token' do
          let_it_be(:project_bot_user) { create(:user, :project_bot) }
          let_it_be(:access_token) { create(:personal_access_token, user: project_bot_user) }

          context 'when the token belongs to the project' do
            before do
              project.add_maintainer(project_bot_user)
            end

            it_behaves_like 'with a valid access token'
          end

          it_behaves_like 'with an invalid access token'
        end

Serena Fang's avatar
Serena Fang committed
437
        context 'when in a group namespace' do
438 439 440
          let_it_be(:group) { create(:group) }
          let_it_be(:project) { create(:project, group: group) }

Serena Fang's avatar
Serena Fang committed
441 442 443 444 445 446 447 448 449 450
          context 'when using a project access token' do
            let_it_be(:project_bot_user) { create(:user, :project_bot) }
            let_it_be(:access_token) { create(:personal_access_token, user: project_bot_user) }

            context 'when token user belongs to the project' do
              before do
                project.add_maintainer(project_bot_user)
              end

              it_behaves_like 'with a valid access token'
451 452
            end

Serena Fang's avatar
Serena Fang committed
453
            it_behaves_like 'with an invalid access token'
454 455
          end

Serena Fang's avatar
Serena Fang committed
456 457 458
          context 'when using a group access token' do
            let_it_be(:project_bot_user) { create(:user, name: 'Group token bot', email: "group_#{group.id}_bot@example.com", username: "group_#{group.id}_bot", user_type: :project_bot) }
            let_it_be(:access_token) { create(:personal_access_token, user: project_bot_user) }
459

Serena Fang's avatar
Serena Fang committed
460 461 462 463
            context 'when the token belongs to the group' do
              before do
                group.add_maintainer(project_bot_user)
              end
464

Serena Fang's avatar
Serena Fang committed
465
              it_behaves_like 'with a valid access token'
466 467
            end

Serena Fang's avatar
Serena Fang committed
468
            it_behaves_like 'with an invalid access token'
469 470
          end
        end
471
      end
472 473 474
    end

    context 'while using regular user and password' do
475 476 477 478 479
      it 'fails for a blocked user' do
        user = create(
          :user,
          :blocked,
          username: 'normal_user',
Zhu Shung's avatar
Zhu Shung committed
480
          password: Gitlab::Password.test_default
481 482 483
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
484
          .to have_attributes(auth_failure)
485 486
      end

487 488
      context 'when 2fa is enabled globally' do
        let_it_be(:user) do
Zhu Shung's avatar
Zhu Shung committed
489
          create(:user, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago)
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
        end

        before do
          stub_application_setting(require_two_factor_authentication: true)
        end

        it 'fails if grace period expired' do
          stub_application_setting(two_factor_grace_period: 0)

          expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') }
            .to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
        end

        it 'goes through if grace period is not expired yet' do
          stub_application_setting(two_factor_grace_period: 72)

          expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
            .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
        end
      end

      context 'when 2fa is enabled personally' do
        let(:user) do
Zhu Shung's avatar
Zhu Shung committed
513
          create(:user, :two_factor, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago)
514 515 516 517 518 519 520 521
        end

        it 'fails' do
          expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') }
            .to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
        end
      end

522
      it 'goes through lfs authentication' do
523 524 525
        user = create(
          :user,
          username: 'normal_user',
Zhu Shung's avatar
Zhu Shung committed
526
          password: Gitlab::Password.test_default
527 528 529
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
530
          .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
531 532
      end

533
      it 'goes through oauth authentication when the username is oauth2' do
534 535 536
        user = create(
          :user,
          username: 'oauth2',
Zhu Shung's avatar
Zhu Shung committed
537
          password: Gitlab::Password.test_default
538 539 540
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
541
          .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
542
      end
543 544 545 546 547
    end

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

548
      expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to have_attributes(auth_failure)
549
    end
550 551

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

554
      expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
555
    end
556 557

    context 'while using deploy tokens' do
558 559 560
      shared_examples 'registry token scope' do
        it 'fails when login is not valid' do
          expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
561
            .to have_attributes(auth_failure)
562 563 564 565
        end

        it 'fails when token is not valid' do
          expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
566
            .to have_attributes(auth_failure)
567 568 569 570
        end

        it 'fails if token is nil' do
          expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip'))
571
            .to have_attributes(auth_failure)
572 573 574 575
        end

        it 'fails if token is not related to project' do
          expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip'))
576
            .to have_attributes(auth_failure)
577 578 579 580 581 582 583
        end

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

          expect(deploy_token.revoked?).to be_truthy
          expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip'))
584
            .to have_attributes(auth_failure)
585 586 587
        end
      end

588
      shared_examples 'deploy token with disabled feature' do
589 590 591 592 593 594 595
        context 'when registry disabled' do
          before do
            stub_container_registry_config(enabled: false)
          end

          it 'fails when login and token are valid' do
            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
596
              .to have_attributes(auth_failure)
597 598
          end
        end
599 600 601 602 603 604

        context 'when repository is disabled' do
          let(:project) { create(:project, :repository_disabled) }

          it 'fails when login and token are valid' do
            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
605
              .to have_attributes(auth_failure)
606 607
          end
        end
608 609
      end

610 611
      context 'when deploy token and user have the same username' do
        let(:username) { 'normal_user' }
Zhu Shung's avatar
Zhu Shung committed
612
        let(:user) { create(:user, username: username, password: Gitlab::Password.test_default) }
613 614 615
        let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }

        it 'succeeds for the token' do
616
          auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] }
617 618

          expect(gl_auth.find_for_git_client(username, deploy_token.token, project: project, ip: 'ip'))
619
            .to have_attributes(auth_success)
620 621 622
        end

        it 'succeeds for the user' do
623
          auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities }
624

Zhu Shung's avatar
Zhu Shung committed
625
          expect(gl_auth.find_for_git_client(username, Gitlab::Password.test_default, project: project, ip: 'ip'))
626
            .to have_attributes(auth_success)
627 628 629 630 631 632 633
        end
      end

      context 'when deploy tokens have the same username' do
        context 'and belong to the same project' do
          let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
          let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
634
          let(:auth_success) { { actor: read_repository, project: project, type: :deploy_token, authentication_abilities: [:download_code] } }
635 636 637

          it 'succeeds for the right token' do
            expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
638
              .to have_attributes(auth_success)
639 640 641 642
          end

          it 'fails for the wrong token' do
            expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip'))
643
              .not_to have_attributes(auth_success)
644 645 646 647
          end
        end

        context 'and belong to different projects' do
648
          let_it_be(:other_project) { create(:project) }
649

650
          let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
651
          let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [other_project]) }
652
          let(:auth_success) { { actor: read_repository, project: other_project, type: :deploy_token, authentication_abilities: [:download_code] } }
653 654

          it 'succeeds for the right token' do
655
            expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: other_project, ip: 'ip'))
656
              .to have_attributes(auth_success)
657 658 659
          end

          it 'fails for the wrong token' do
660
            expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: other_project, ip: 'ip'))
661
              .not_to have_attributes(auth_success)
662 663 664 665
          end
        end
      end

666
      context 'when the deploy token has read_repository as scope' do
667
        let(:deploy_token) { create(:deploy_token, read_registry: false, projects: [project]) }
668
        let(:login) { deploy_token.username }
669

670
        it 'succeeds when login and token are valid' do
671
          auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] }
672

673
          expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
674
            .to have_attributes(auth_success)
675
        end
676

677 678
        it 'succeeds when custom login and token are valid' do
          deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project])
679
          auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] }
680 681

          expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip'))
682
            .to have_attributes(auth_success)
683 684
        end

685 686 687 688 689 690
        it 'does not attempt to rate limit unique IPs for a deploy token' do
          expect(Gitlab::Auth::UniqueIpsLimiter).not_to receive(:limit_user!)

          gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')
        end

691 692
        it 'fails when login is not valid' do
          expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
693
            .to have_attributes(auth_failure)
694 695 696 697
        end

        it 'fails when token is not valid' do
          expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip'))
698
            .to have_attributes(auth_failure)
699 700
        end

701
        it 'fails if token is nil' do
702
          expect(gl_auth.find_for_git_client(login, nil, project: project, ip: 'ip'))
703
            .to have_attributes(auth_failure)
704
        end
705

706
        it 'fails if token is not related to project' do
707
          another_deploy_token = create(:deploy_token)
708
          expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project, ip: 'ip'))
709
            .to have_attributes(auth_failure)
710
        end
711

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

715 716
          expect(deploy_token.revoked?).to be_truthy
          expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip'))
717
            .to have_attributes(auth_failure)
718 719 720
        end
      end

721 722 723 724 725 726 727 728
      context 'when the deploy token is of group type' do
        let(:project_with_group) { create(:project, group: create(:group)) }
        let(:deploy_token) { create(:deploy_token, :group, read_repository: true, groups: [project_with_group.group]) }
        let(:login) { deploy_token.username }

        subject { gl_auth.find_for_git_client(login, deploy_token.token, project: project_with_group, ip: 'ip') }

        it 'succeeds when login and a group deploy token are valid' do
729
          auth_success = { actor: deploy_token, project: project_with_group, type: :deploy_token, authentication_abilities: [:download_code, :read_container_image] }
730

731
          expect(subject).to have_attributes(auth_success)
732
        end
733 734 735 736 737

        it 'fails if token is not related to group' do
          another_deploy_token = create(:deploy_token, :group, read_repository: true)

          expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project_with_group, ip: 'ip'))
738
            .to have_attributes(auth_failure)
739
        end
740 741
      end

742
      context 'when the deploy token has read_registry as a scope' do
743
        let(:deploy_token) { create(:deploy_token, read_repository: false, projects: [project]) }
744
        let(:login) { deploy_token.username }
745 746 747 748 749 750

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

751
          it 'succeeds when login and a project token are valid' do
752
            auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:read_container_image] }
753

754
            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
755
              .to have_attributes(auth_success)
756 757
          end

758 759
          it_behaves_like 'registry token scope'
        end
760

761
        it_behaves_like 'deploy token with disabled feature'
762
      end
763

764 765 766
      context 'when the deploy token has write_registry as a scope' do
        let_it_be(:deploy_token) { create(:deploy_token, write_registry: true, read_repository: false, read_registry: false, projects: [project]) }
        let_it_be(:login) { deploy_token.username }
767

768 769 770
        context 'when registry enabled' do
          before do
            stub_container_registry_config(enabled: true)
771 772
          end

773
          it 'succeeds when login and a project token are valid' do
774
            auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:create_container_image] }
775

776
            expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
777
              .to have_attributes(auth_success)
778
          end
779

780
          it_behaves_like 'registry token scope'
781
        end
782

783
        it_behaves_like 'deploy token with disabled feature'
784
      end
785
    end
786 787
  end

Thong Kuah's avatar
Thong Kuah committed
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
  describe '#build_access_token_check' do
    subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: '1.2.3.4') }

    let_it_be(:user) { create(:user) }

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

      it 'executes query using primary database' do
        expect(Ci::Build).to receive(:find_by_token).with(build.token).and_wrap_original do |m, *args|
          expect(::Gitlab::Database::LoadBalancing::Session.current.use_primary?).to eq(true)
          m.call(*args)
        end

        expect(subject).to be_a(Gitlab::Auth::Result)
        expect(subject.actor).to eq(user)
        expect(subject.project).to eq(build.project)
        expect(subject.type).to eq(:build)
      end
    end
  end

810
  describe 'find_with_user_password' do
811 812
    let!(:user) do
      create(:user,
813 814 815
             username: username,
             password: password,
             password_confirmation: password)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
816
    end
817

818
    let(:username) { 'John' } # username isn't lowercase, test this
Zhu Shung's avatar
Zhu Shung committed
819
    let(:password) { Gitlab::Password.test_default }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
820

821
    it "finds user by valid login/password" do
822
      expect(gl_auth.find_with_user_password(username, password)).to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
823 824
    end

825
    it 'finds user by valid email/password with case-insensitive email' do
826
      expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
827 828
    end

829
    it 'finds user by valid username/password with case-insensitive username' do
830
      expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
831 832
    end

833
    it "does not find user with invalid password" do
834
      password = 'wrong'
835
      expect(gl_auth.find_with_user_password(username, password)).not_to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
836 837
    end

838
    it "does not find user with invalid login" do
839
      user = 'wrong'
840
      expect(gl_auth.find_with_user_password(username, password)).not_to eql user
841
    end
842

Pawel Chojnacki's avatar
Pawel Chojnacki committed
843 844
    include_examples 'user login operation with unique ip limit' do
      def operation
845
        expect(gl_auth.find_with_user_password(username, password)).to eq(user)
Pawel Chojnacki's avatar
Pawel Chojnacki committed
846 847 848
      end
    end

849 850 851
    it 'finds the user in deactivated state' do
      user.deactivate!

852
      expect(gl_auth.find_with_user_password(username, password)).to eql user
853 854
    end

855 856 857
    it "does not find user in blocked state" do
      user.block

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

861 862 863 864 865 866
    it 'does not find user in locked state' do
      user.lock_access!

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

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

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

873 874 875
    it 'does not find user in blocked_pending_approval state' do
      user.block_pending_approval

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

879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
    context 'with increment_failed_attempts' do
      wrong_password = 'incorrect_password'

      it 'increments failed_attempts when true and password is incorrect' do
        expect do
          gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
          user.reload
        end.to change(user, :failed_attempts).from(0).to(1)
      end

      it 'resets failed_attempts when true and password is correct' do
        user.failed_attempts = 2
        user.save

        expect do
          gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
          user.reload
        end.to change(user, :failed_attempts).from(2).to(0)
      end

      it 'does not increment failed_attempts by default' do
        expect do
          gl_auth.find_with_user_password(username, wrong_password)
          user.reload
        end.not_to change(user, :failed_attempts)
      end

906
      context 'when the database is read-only' do
907
        before do
908
          allow(Gitlab::Database).to receive(:read_only?).and_return(true)
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
        end

        it 'does not increment failed_attempts when true and password is incorrect' do
          expect do
            gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
            user.reload
          end.not_to change(user, :failed_attempts)
        end

        it 'does not reset failed_attempts when true and password is correct' do
          user.failed_attempts = 2
          user.save

          expect do
            gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
            user.reload
          end.not_to change(user, :failed_attempts)
        end
      end
    end

930
    context "with ldap enabled" do
931
      before do
932
        allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
933
      end
934 935

      it "tries to autheticate with db before ldap" do
936
        expect(Gitlab::Auth::Ldap::Authentication).not_to receive(:login)
937

938 939 940 941
        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
942
        expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil)
943

Zhu Shung's avatar
Zhu Shung committed
944
        expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to be_nil
945 946
      end

947
      it "find new user by using ldap as fallback to for authentication" do
948
        expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user)
949

Zhu Shung's avatar
Zhu Shung committed
950
        expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to eq(user)
951 952
      end
    end
953 954 955 956 957 958 959 960 961 962 963 964

    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
965
          allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
966 967 968 969 970 971 972
        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
973
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
974

975 976 977 978 979 980 981 982
  describe ".resource_bot_scopes" do
    subject { described_class.resource_bot_scopes }

    it { is_expected.to include(*described_class::API_SCOPES - [:read_user]) }
    it { is_expected.to include(*described_class::REPOSITORY_SCOPES) }
    it { is_expected.to include(*described_class.registry_scopes) }
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
983 984
  private

985 986
  def expect_results_with_abilities(personal_access_token, abilities, success = true)
    expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip'))
987
      .to have_attributes(actor: personal_access_token&.user, project: nil, type: personal_access_token.nil? ? nil : :personal_access_token, authentication_abilities: abilities)
988
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
989
end